Pulse/internal/api/contract_test.go
2026-07-02 11:50:03 +01:00

19624 lines
744 KiB
Go

package api
import (
"bytes"
"context"
"crypto/ed25519"
"crypto/tls"
"encoding/base64"
"encoding/json"
"fmt"
"net/http"
"net/http/httptest"
"net/url"
"os"
"path/filepath"
"reflect"
"regexp"
"sort"
"strings"
"testing"
"time"
"github.com/golang-jwt/jwt/v5"
"github.com/gorilla/websocket"
"github.com/rcourtman/pulse-go-rewrite/internal/actionplanner"
"github.com/rcourtman/pulse-go-rewrite/internal/agentcapabilities"
"github.com/rcourtman/pulse-go-rewrite/internal/agentexec"
"github.com/rcourtman/pulse-go-rewrite/internal/ai"
"github.com/rcourtman/pulse-go-rewrite/internal/ai/approval"
"github.com/rcourtman/pulse-go-rewrite/internal/ai/chat"
"github.com/rcourtman/pulse-go-rewrite/internal/ai/correlation"
"github.com/rcourtman/pulse-go-rewrite/internal/ai/memory"
"github.com/rcourtman/pulse-go-rewrite/internal/ai/providers"
"github.com/rcourtman/pulse-go-rewrite/internal/ai/unified"
"github.com/rcourtman/pulse-go-rewrite/internal/alerts"
"github.com/rcourtman/pulse-go-rewrite/internal/config"
"github.com/rcourtman/pulse-go-rewrite/internal/license/entitlements"
"github.com/rcourtman/pulse-go-rewrite/internal/mock"
"github.com/rcourtman/pulse-go-rewrite/internal/models"
"github.com/rcourtman/pulse-go-rewrite/internal/monitoring"
"github.com/rcourtman/pulse-go-rewrite/internal/notifications"
"github.com/rcourtman/pulse-go-rewrite/internal/recovery"
"github.com/rcourtman/pulse-go-rewrite/internal/relay"
"github.com/rcourtman/pulse-go-rewrite/internal/telemetry"
"github.com/rcourtman/pulse-go-rewrite/internal/truenas"
"github.com/rcourtman/pulse-go-rewrite/internal/unifiedresources"
"github.com/rcourtman/pulse-go-rewrite/internal/updates"
"github.com/rcourtman/pulse-go-rewrite/internal/vmware"
agentshost "github.com/rcourtman/pulse-go-rewrite/pkg/agents/host"
"github.com/rcourtman/pulse-go-rewrite/pkg/aicontracts"
"github.com/rcourtman/pulse-go-rewrite/pkg/audit"
authpkg "github.com/rcourtman/pulse-go-rewrite/pkg/auth"
"github.com/rcourtman/pulse-go-rewrite/pkg/cloudauth"
pkglicensing "github.com/rcourtman/pulse-go-rewrite/pkg/licensing"
licensetestsupport "github.com/rcourtman/pulse-go-rewrite/pkg/licensing/testsupport"
"github.com/rcourtman/pulse-go-rewrite/pkg/metrics"
"github.com/rcourtman/pulse-go-rewrite/pkg/reporting"
"github.com/rs/zerolog"
tmock "github.com/stretchr/testify/mock"
)
type resourceContractSnapshot struct {
ID string
Name string
Type string
}
func TestContract_AlertDeliveryDiagnosisRouteIsReadOnlyMonitoringRead(t *testing.T) {
source, err := os.ReadFile("alerts.go")
if err != nil {
t.Fatalf("read alerts.go: %v", err)
}
src := string(source)
required := []string{
`case path == "delivery-diagnosis" && r.Method == http.MethodGet:`,
`ensureScope(w, r, config.ScopeMonitoringRead)`,
`h.GetAlertDeliveryDiagnosis(w, r)`,
`manager.DiagnoseAlertDelivery(alertIdentifier)`,
}
for _, snippet := range required {
if !strings.Contains(src, snippet) {
t.Fatalf("alert delivery diagnosis route contract missing %q", snippet)
}
}
}
func TestContract_AlertDeliveryDiagnosisPayloadShape(t *testing.T) {
replayAt := time.Date(2026, 6, 29, 12, 30, 0, 0, time.UTC)
diagnosis := alerts.AlertDeliveryDiagnosis{
AlertIdentifier: "canonical:a1",
AlertID: "a1",
TrackingKey: "node/a1/cpu",
Status: alerts.AlertDeliveryStatusDeferred,
Reason: alerts.AlertDeliveryReasonQuietHours + ":performance",
Message: "Alert delivery is deferred by quiet-hours policy and will be replayed later.",
AlertType: "cpu",
Level: alerts.AlertLevelCritical,
ResourceID: "node-1",
ResourceName: "node-1",
NotificationsEnabled: true,
ActivationState: string(alerts.ActivationActive),
CooldownMinutes: 5,
MaxAlertsHour: 10,
RecentAlertsInHour: 2,
FlappingActive: false,
FlappingHistoryInWindow: 1,
FlappingThreshold: 3,
FlappingWindowSeconds: 300,
QuietHoursReplayAt: &replayAt,
}
payload, err := json.Marshal(diagnosis)
if err != nil {
t.Fatalf("marshal alert delivery diagnosis: %v", err)
}
body := string(payload)
for _, field := range []string{
`"alertIdentifier":"canonical:a1"`,
`"trackingKey":"node/a1/cpu"`,
`"status":"deferred"`,
`"reason":"quiet_hours:performance"`,
`"notificationsEnabled":true`,
`"cooldownMinutes":5`,
`"recentAlertsInHour":2`,
`"flappingHistoryInWindow":1`,
`"quietHoursReplayAt":"2026-06-29T12:30:00Z"`,
} {
if !strings.Contains(body, field) {
t.Fatalf("alert delivery diagnosis payload missing %s in %s", field, body)
}
}
}
func TestContract_PMGInstancesEndpointUsesUnifiedReadStatePayload(t *testing.T) {
now := time.Now().UTC()
source := models.PMGInstance{
ID: "pmg-contract-1",
Name: "gateway-contract",
Host: "https://pmg-contract.example.com:8006",
GuestURL: "https://pmg-contract.example.com/quarantine",
Status: "online",
Version: "8.2",
Nodes: []models.PMGNodeStatus{{
Name: "pmg-node-contract",
Status: "online",
QueueStatus: &models.PMGQueueStatus{
Incoming: 4,
Total: 10,
OldestAge: 600,
UpdatedAt: now,
},
}},
MailStats: &models.PMGMailStats{
CountTotal: 1000,
JunkIn: 10,
PregreetRejects: 13,
UpdatedAt: now,
},
MailCount: []models.PMGMailCountPoint{{Timestamp: now, Count: 1000, Timeframe: "hour", Index: 1}},
DomainStats: []models.PMGDomainStat{{Domain: "example.com", MailCount: 100, Bytes: 2048}},
DomainStatsAsOf: now,
ConnectionHealth: "connected",
LastSeen: now,
LastUpdated: now,
}
registry := unifiedresources.NewRegistry(nil)
registry.IngestSnapshot(models.StateSnapshot{PMGInstances: []models.PMGInstance{source}})
monitor := &monitoring.Monitor{}
setUnexportedField(t, monitor, "resourceStore", unifiedresources.NewMonitorAdapter(registry))
router := &Router{monitor: monitor}
req := httptest.NewRequest(http.MethodGet, "/api/pmg/instances?id=pmg-contract-1", nil)
rec := httptest.NewRecorder()
router.handleListPMGInstances(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("status = %d, body=%s", rec.Code, rec.Body.String())
}
var resp PMGInstancesResponse
if err := json.NewDecoder(rec.Body).Decode(&resp); err != nil {
t.Fatalf("decode PMG contract response: %v", err)
}
if resp.Meta.Total != 1 || len(resp.Data) != 1 {
t.Fatalf("expected one PMG instance, got total=%d len=%d", resp.Meta.Total, len(resp.Data))
}
got := resp.Data[0]
if got.ID != source.ID || got.Host != source.Host || got.GuestURL != source.GuestURL {
t.Fatalf("PMG identity and URL contract not preserved: %+v", got)
}
if len(got.Nodes) != 1 || got.Nodes[0].QueueStatus == nil || got.Nodes[0].QueueStatus.OldestAge != 600 {
t.Fatalf("PMG queue contract not preserved: %+v", got.Nodes)
}
if got.MailStats == nil || got.MailStats.CountTotal != 1000 || got.MailStats.JunkIn != 10 || got.MailStats.PregreetRejects != 13 {
t.Fatalf("PMG mail stats contract not preserved: %+v", got.MailStats)
}
if len(got.MailCount) != 1 || got.MailCount[0].Index != 1 || len(got.DomainStats) != 1 || got.DomainStats[0].Bytes != 2048 {
t.Fatalf("PMG historical/domain contract not preserved: mail=%+v domains=%+v", got.MailCount, got.DomainStats)
}
}
func TestContract_MockAvailabilityTargetsUseSavedTargetPayloads(t *testing.T) {
previousMock := mock.IsMockEnabled()
if err := mock.SetEnabled(true); err != nil {
t.Fatalf("enable mock mode: %v", err)
}
t.Cleanup(func() { _ = mock.SetEnabled(previousMock) })
targets := mockAvailabilityTargetResponses()
if len(targets) < 4 {
t.Fatalf("expected mock availability target payloads, got %d", len(targets))
}
seen := map[string]availabilityTargetResponse{}
for _, target := range targets {
if target.ID == "" {
t.Fatal("mock availability target payload must keep a saved target id")
}
if target.Status == nil {
t.Fatalf("mock availability target %q must include probe status", target.ID)
}
if target.Status.TargetID != target.ID {
t.Fatalf("mock availability target %q status target id drifted to %q", target.ID, target.Status.TargetID)
}
seen[target.ID] = target
}
mqtt, ok := seen["mock-availability-mqtt-meter"]
if !ok {
t.Fatal("mock MQTT availability target missing from API payload contract")
}
if mqtt.Protocol != config.AvailabilityProbeTCP || mqtt.Port != 1883 {
t.Fatalf("mock MQTT availability target must remain a saved TCP:1883 target, got protocol=%q port=%v", mqtt.Protocol, mqtt.Port)
}
response, ok := mockAvailabilityTestResponse("mock-availability-mqtt-meter")
if !ok {
t.Fatal("saved mock availability test response missing")
}
if !response.Success || response.LatencyMillis <= 0 || response.Error != "" {
t.Fatalf("mock MQTT saved-test response must be successful with latency and no error: %+v", response)
}
}
func TestContract_MockDiscoveryEndpointsUseCanonicalPayloads(t *testing.T) {
previousMock := mock.IsMockEnabled()
if err := mock.SetEnabled(true); err != nil {
t.Fatalf("enable mock mode: %v", err)
}
t.Cleanup(func() { _ = mock.SetEnabled(previousMock) })
handlers := NewDiscoveryHandlers(nil, nil)
listReq := httptest.NewRequest(http.MethodGet, "/api/discovery", nil)
listRec := httptest.NewRecorder()
handlers.HandleListDiscoveries(listRec, listReq)
if listRec.Code != http.StatusOK {
t.Fatalf("/api/discovery status = %d, body=%s", listRec.Code, listRec.Body.String())
}
var listBody struct {
Discoveries []map[string]any `json:"discoveries"`
Total int `json:"total"`
}
if err := json.NewDecoder(listRec.Body).Decode(&listBody); err != nil {
t.Fatalf("decode discovery list: %v", err)
}
if listBody.Total == 0 || len(listBody.Discoveries) == 0 {
t.Fatal("expected mock discovery list to expose canonical discovery summaries")
}
var dockerDetail map[string]any
for _, summary := range listBody.Discoveries {
if summary["resource_type"] != "docker" {
continue
}
resourceType, _ := summary["resource_type"].(string)
targetID, _ := summary["target_id"].(string)
resourceID, _ := summary["resource_id"].(string)
if resourceType == "" || targetID == "" || resourceID == "" {
t.Fatalf("mock discovery summary lost canonical identity: %#v", summary)
}
detailReq := httptest.NewRequest(http.MethodGet, fmt.Sprintf("/api/discovery/%s/%s/%s", resourceType, targetID, resourceID), nil)
detailRec := httptest.NewRecorder()
handlers.HandleGetDiscovery(detailRec, detailReq)
if detailRec.Code != http.StatusOK {
t.Fatalf("discovery detail status = %d, body=%s", detailRec.Code, detailRec.Body.String())
}
var detail map[string]any
if err := json.NewDecoder(detailRec.Body).Decode(&detail); err != nil {
t.Fatalf("decode discovery detail: %v", err)
}
if !discoveryContractValueEmpty(detail["suggested_url"]) {
dockerDetail = detail
break
}
}
if dockerDetail == nil {
t.Fatalf("expected mock discovery summaries to include Docker workload URL context: %#v", listBody.Discoveries)
}
for _, key := range []string{"service_name", "service_version", "config_paths", "ports", "suggested_url", "cli_access_version"} {
value, ok := dockerDetail[key]
if !ok || discoveryContractValueEmpty(value) {
t.Fatalf("mock discovery detail missing %s: %#v", key, dockerDetail)
}
}
if _, ok := dockerDetail["raw_command_output"]; ok {
t.Fatalf("mock discovery detail exposed raw command output: %#v", dockerDetail)
}
}
func discoveryContractValueEmpty(value any) bool {
switch typed := value.(type) {
case nil:
return true
case string:
return strings.TrimSpace(typed) == ""
case []any:
return len(typed) == 0
case float64:
return typed == 0
default:
return false
}
}
func TestPatrolRemediationCommercialCopyUsesSafeRemediationWording(t *testing.T) {
files := []string{"ai_handlers.go", "router_routes_ai_relay.go"}
for _, file := range files {
source, err := os.ReadFile(file)
if err != nil {
t.Fatalf("read %s: %v", file, err)
}
text := string(source)
if strings.Contains(text, "Pulse Patrol Auto-Fix requires Pulse Pro") ||
strings.Contains(text, "Auto-Fix requires Pulse Pro") {
t.Fatalf("%s must not expose legacy Auto-Fix license copy", file)
}
}
}
func TestContract_PatrolAutonomyCommunityMonitorUpdatePayload(t *testing.T) {
rawToken := "patrol-autonomy-contract-token-123.12345678"
record := newTokenRecord(t, rawToken, []string{config.ScopeSettingsWrite}, nil)
cfg := newTestConfigWithTokens(t, record)
router := NewRouter(cfg, nil, nil, nil, nil, "1.0.0")
body := `{"autonomy_level":"monitor","full_mode_unlocked":true,"investigation_budget":2,"investigation_timeout_sec":30}`
req := httptest.NewRequest(http.MethodPut, "/api/ai/patrol/autonomy", strings.NewReader(body))
req.Header.Set("X-API-Token", rawToken)
rec := httptest.NewRecorder()
router.Handler().ServeHTTP(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("expected monitor autonomy update to be allowed, got %d: %s", rec.Code, rec.Body.String())
}
var resp struct {
Success bool `json:"success"`
Settings PatrolAutonomyResponse `json:"settings"`
}
if err := json.Unmarshal(rec.Body.Bytes(), &resp); err != nil {
t.Fatalf("decode monitor update response: %v", err)
}
if !resp.Success {
t.Fatalf("expected success response, got %+v", resp)
}
if resp.Settings.AutonomyLevel != config.PatrolAutonomyMonitor ||
resp.Settings.FullModeUnlocked ||
resp.Settings.InvestigationBudget != 5 ||
resp.Settings.InvestigationTimeoutSec != 60 {
t.Fatalf("unexpected monitor update settings: %+v", resp.Settings)
}
premiumBody := `{"autonomy_level":"approval","investigation_budget":10,"investigation_timeout_sec":120}`
premiumReq := httptest.NewRequest(http.MethodPut, "/api/ai/patrol/autonomy", strings.NewReader(premiumBody))
premiumReq.Header.Set("X-API-Token", rawToken)
premiumRec := httptest.NewRecorder()
router.Handler().ServeHTTP(premiumRec, premiumReq)
if premiumRec.Code != http.StatusPaymentRequired {
t.Fatalf("expected premium autonomy to require license, got %d: %s", premiumRec.Code, premiumRec.Body.String())
}
if !strings.Contains(premiumRec.Body.String(), "limited to Monitor") {
t.Fatalf("expected premium autonomy payload to explain Community monitor limit, got %s", premiumRec.Body.String())
}
}
func TestContract_AssistantFindingContextUsesModelOnlyHandoff(t *testing.T) {
handlerSource, err := os.ReadFile(filepath.Clean("ai_handler.go"))
if err != nil {
t.Fatalf("read ai_handler.go: %v", err)
}
settingsHandlerSource, err := os.ReadFile(filepath.Clean("ai_handlers.go"))
if err != nil {
t.Fatalf("read ai_handlers.go: %v", err)
}
chatServiceSource, err := os.ReadFile(filepath.Clean("../ai/chat/service.go"))
if err != nil {
t.Fatalf("read chat service: %v", err)
}
chatSessionSource, err := os.ReadFile(filepath.Clean("../ai/chat/session.go"))
if err != nil {
t.Fatalf("read chat session store: %v", err)
}
chatTypesSource, err := os.ReadFile(filepath.Clean("../ai/chat/types.go"))
if err != nil {
t.Fatalf("read chat types: %v", err)
}
patrolHandoffSource, err := os.ReadFile(filepath.Clean("../ai/patrol_assistant_handoff.go"))
if err != nil {
t.Fatalf("read Patrol Assistant handoff model: %v", err)
}
toolsQuerySource, err := os.ReadFile(filepath.Clean("../ai/tools/tools_query.go"))
if err != nil {
t.Fatalf("read AI tools query runtime: %v", err)
}
handlerText := string(handlerSource)
for _, required := range []string{
"requestSuppliedSessionID := requestSessionID != \"\"",
`if findingID == "" && requestSuppliedSessionID`,
`svc.GetModelHandoffFindingID(ctx, requestSessionID)`,
`svc.GetModelHandoffMetadata(ctx, requestSessionID)`,
`if requestSuppliedSessionID`,
`svc.ClearModelHandoffContext(ctx, requestSessionID)`,
`HandoffActions []chat.HandoffAction ` + "`json:\"handoff_actions,omitempty\"`",
`HandoffMetadata chat.HandoffMetadata ` + "`json:\"handoff_metadata,omitempty\"`",
"handoffActions := normalizeChatRequestHandoffActions(req.HandoffActions)",
"handoffMetadata := chat.NormalizeHandoffMetadata(req.HandoffMetadata)",
"storedMetadata = chat.NormalizeHandoffMetadata(storedMetadata)",
`if storedMetadata.Kind == "patrol_run"`,
"handoffMetadata = storedMetadata",
"h.getPatrolRunForHandoff(ctx, handoffMetadata.RunID)",
"airuntime.BuildPatrolRunAssistantHandoff(run)",
"chatRequestHandoffActionLimit",
"requestHandoffContext := handoffContext",
"requestHandoffResources := handoffResources",
"requestHandoffActions := handoffActions",
"handoffActions = mergeUnifiedFindingRequestHandoffActions(f, buildUnifiedFindingHandoffActions(f, orgID), requestHandoffActions)",
"handoffContext = mergeUnifiedFindingRequestHandoffContext(buildUnifiedFindingChatContext(f, store, handoffActions), requestHandoffContext, f.ID)",
"handoffResources = mergeUnifiedFindingRequestHandoffResources(f, buildUnifiedFindingHandoffResources(f, store), requestHandoffResources)",
"safePatrolFindingRequestHandoffContext",
"Product Handoff Boundary",
"livePatrolApprovalForFinding(f.ID, orgID)",
"chat.HydrateHandoffActionFromApproval(&action, liveApproval)",
"ApprovalRequestedAt",
"ApprovalExpiresAt",
"func chatAutonomousModeForFindingHandoff(requested *bool, findingID, handoffContext string, handoffResources []chat.HandoffResource, handoffActions []chat.HandoffAction, handoffMetadata chat.HandoffMetadata) *bool",
`if strings.TrimSpace(findingID) != ""`,
"chatApprovalRequiredAutonomousMode()",
"AutonomousMode: chatAutonomousModeForFindingHandoff(req.AutonomousMode, findingID, handoffContext, handoffResources, handoffActions, handoffMetadata)",
`appendChatContextLine(&b, "Finding Status", unifiedFindingChatStatus(f, time.Now()))`,
`appendChatContextLine(&b, "Finding Detected At", f.DetectedAt.Format(time.RFC3339))`,
`appendChatContextLine(&b, "Finding Last Seen At", f.LastSeenAt.Format(time.RFC3339))`,
`appendChatContextLine(&b, "Finding Resolved At", f.ResolvedAt.Format(time.RFC3339))`,
`appendChatContextLine(&b, "Finding Snoozed Until", f.SnoozedUntil.Format(time.RFC3339))`,
`appendChatContextLine(&b, "Finding Dismissed Reason", f.DismissedReason)`,
`appendChatContextLine(&b, "Finding Suppressed", "true")`,
`appendChatContextLine(&b, "Finding Times Raised", strconv.Itoa(f.TimesRaised))`,
`appendChatContextLine(&b, "AI Context", f.AIContext)`,
`appendChatContextLine(&b, "Root Cause ID", f.RootCauseID)`,
`appendStringListChatContext(&b, "Correlated Finding", f.CorrelatedIDs)`,
"appendUnifiedFindingRelatedContext(&b, f, lookup)",
"[Related Finding Context]",
"Related Finding Boundary",
"formatUnifiedFindingBriefingRecency(f)",
"latest lifecycle",
`appendChatContextLine(&b, "Remediation ID", f.RemediationID)`,
`appendChatContextLine(&b, "Last Investigated At", f.LastInvestigatedAt.Format(time.RFC3339))`,
`appendChatContextLine(&b, "Investigation Attempts", strconv.Itoa(f.InvestigationAttempts))`,
`appendChatContextLine(&b, "Loop State", f.LoopState)`,
`appendChatContextLine(&b, "Regression Count", strconv.Itoa(f.RegressionCount))`,
`appendChatContextLine(&b, "Last Regression At", f.LastRegressionAt.Format(time.RFC3339))`,
"appendUnifiedFindingLifecycleEventContext(&b, f.Lifecycle)",
"formatUnifiedFindingLatestLifecycleEvent(f.Lifecycle)",
"[Finding Lifecycle Context]",
"Lifecycle Boundary",
"appendUnifiedFindingModelBriefingContext(&b, f, handoffActions)",
"[Finding Briefing]",
`appendChatContextLine(b, "Recency", formatUnifiedFindingBriefingRecencyFacts(f))`,
"formatUnifiedFindingBriefingRecencyFacts(f)",
`appendChatContextLine(b, "Evidence Snapshot", evidence)`,
`appendChatContextLine(b, "Verification", verification)`,
"formatInvestigationRecordEvidenceBriefing",
"formatBriefingStringList",
`appendChatContextLine(b, "Governed Action Context", actionContext)`,
"unifiedFindingBriefingActionContext(f, handoffActions)",
"unifiedFindingPrimaryHandoffAction(f, handoffActions)",
"Latest Lifecycle Event",
"Model Boundary",
`return "suppressed"`,
"Prompt:",
"HandoffContext: handoffContext",
"HandoffResources: handoffResources",
"HandoffActions: handoffActions",
"HandoffMetadata: handoffMetadata",
"SuppressSessionEvent: true",
} {
if !strings.Contains(handlerText, required) {
t.Fatalf("ai_handler.go must preserve model-only finding handoff contract: missing %q", required)
}
}
if strings.Contains(handlerText, "prompt = findingCtx +") {
t.Fatal("ai_handler.go must not prepend finding context into the persisted prompt")
}
for _, forbidden := range []string{
"Recommended Next Step",
"unifiedFindingBriefingNextStep",
} {
if strings.Contains(handlerText, forbidden) {
t.Fatalf("ai_handler.go must not synthesize product-authored handoff recommendations: found %q", forbidden)
}
}
settingsHandlerText := string(settingsHandlerSource)
for _, required := range []string{
"actionAuditStore: chatService.GetActionAuditStore()",
"tools.AttachApprovalActionPlan(req, time.Now().UTC())",
"tools.RecordPendingApprovalAction(a.actionAuditStore, req)",
} {
if !strings.Contains(settingsHandlerText, required) {
t.Fatalf("ai_handlers.go must preserve Patrol queued-fix action-audit seeding: missing %q", required)
}
}
patrolHandoffText := string(patrolHandoffSource)
for _, required := range []string{
"func BuildPatrolRunAssistantHandoff(run PatrolRunRecord) PatrolRunAssistantHandoff",
"[Patrol Run Context]",
"Source: Pulse Patrol run history",
"patrolRunAssistantHandoffResources",
"sanitizePatrolRunAnalysis",
"Model Boundary",
} {
if !strings.Contains(patrolHandoffText, required) {
t.Fatalf("Patrol runtime must own safe Assistant run handoff context: missing %q", required)
}
}
chatServiceText := string(chatServiceSource)
for _, required := range []string{
"handoffContext := strings.TrimSpace(req.HandoffContext)",
"handoffFindingID := strings.TrimSpace(req.FindingID)",
"handoffMetadata := NormalizeHandoffMetadata(req.HandoffMetadata)",
"handoffMetadataCarriesEnvelope",
"handoffMetadata.Kind == sessionHandoffKindResourceContext",
"hasRequestHandoffEnvelope",
"sessions.SetModelHandoffEnvelope(session.ID, handoffFindingID, handoffContext, handoffResources, handoffActions, handoffMetadata)",
"ClearModelHandoffContext",
"handoffResources := normalizeHandoffResources(req.HandoffResources)",
"GetModelHandoffFindingID",
"GetModelHandoffMetadata",
"sessions.GetModelHandoffEnvelope(session.ID)",
"handoffContext = storedContext",
"handoffResources = storedResources",
"sessions.SetModelHandoffActions(session.ID, handoffActions)",
"handoffActions = storedActions",
"GetActionAuditStore",
"refreshHandoffActionStatus(handoffActions, s.orgID, s.actionAuditStore)",
"HydrateHandoffActionFromApproval(&actions[idx], req)",
"func HydrateHandoffActionFromApproval(action *HandoffAction, req *approval.ApprovalRequest)",
"approval.RequesterForRequest(req)",
"action.ActionRequestedBy = requestedBy",
"mergeHandoffResourcePolicyContext(handoffContext, handoffResources, handoffResourceProvider)",
"mergeHandoffResourceStateContext(handoffContext, handoffResources, handoffResourceProvider)",
"mergeHandoffResourceRelationshipContext(handoffContext, handoffResources, handoffResourceProvider)",
"mergeHandoffResourceTimelineContext(handoffContext, handoffResources, handoffResourceProvider, s.actionAuditStore, time.Now())",
"handoffContext = mergeHandoffActionContext(handoffContext, handoffActions)",
"sanitizeHandoffContextForResourcePolicy(handoffContext, handoffResources, handoffResourceProvider)",
"Action Reference",
"Action State",
"Action Plan Expires At",
"Action Dry Run Summary",
"Action Result",
"s.hydrateHandoffResources(session.ID, handoffResources, sessions, unifiedResourceProvider)",
"injectHandoffContextIntoLatestUserMessage(messages, handoffContext)",
"Resource Policy Context",
"Policy Boundary",
"Resource State Context",
"labelPrefix+\" Status\"",
"labelPrefix+\" Capabilities\"",
"Resource State Boundary",
"Resource Relationship Context",
"Relationship Boundary",
"Timeline Boundary",
"Approval Status",
"Action Boundary",
"User message: ",
} {
if !strings.Contains(chatServiceText, required) {
t.Fatalf("chat service must inject handoff context into model-only turn: missing %q", required)
}
}
chatSessionText := string(chatSessionSource)
if !regexp.MustCompile(`ModelContext\s+\*sessionModelContext`).MatchString(chatSessionText) {
t.Fatalf("chat session store must persist model-only handoff metadata outside messages: missing %q", "ModelContext *sessionModelContext")
}
for _, required := range []string{
"SetModelHandoffFindingID",
"SetModelHandoffEnvelope",
"GetModelHandoffFindingID",
"GetModelHandoffMetadata",
"ClearModelHandoffContext",
"HandoffFindingID string",
"SetModelHandoffContext",
"GetModelHandoffContext",
"GetModelHandoffEnvelope",
"SetModelHandoffResources",
"GetModelHandoffResources",
"HandoffResources []HandoffResource",
"SetModelHandoffActions",
"GetModelHandoffActions",
"HandoffActions []HandoffAction",
"SetModelHandoffMetadata",
"HandoffMetadata HandoffMetadata",
} {
if !strings.Contains(chatSessionText, required) {
t.Fatalf("chat session store must persist model-only handoff metadata outside messages: missing %q", required)
}
}
chatTypesText := string(chatTypesSource)
for _, required := range []string{
"type HandoffResource struct",
"HandoffResources []HandoffResource",
"type HandoffAction struct",
"HandoffActions []HandoffAction",
"type HandoffMetadata struct",
"HandoffMetadata HandoffMetadata",
"ApprovalStatus",
"ActionID",
"ActionState",
"ActionRequestedBy",
"ActionPlanExpiresAt",
"ActionDryRunSummary",
"ActionResult",
} {
if !strings.Contains(chatTypesText, required) {
t.Fatalf("chat request must carry structured handoff resource scope outside messages: missing %q", required)
}
}
toolsQueryText := string(toolsQuerySource)
if !strings.Contains(toolsQueryText, "func CanonicalHandoffResourceRegistration(provider UnifiedResourceProvider") {
t.Fatal("AI tools runtime must own canonical handoff resource registration")
}
if !strings.Contains(toolsQueryText, "func CanonicalHandoffUnifiedResource(provider UnifiedResourceProvider") {
t.Fatal("AI tools runtime must own canonical handoff unified resource resolution")
}
}
func TestContract_AssistantMessageHistoryUsesClientSafeProjection(t *testing.T) {
handlerSource, err := os.ReadFile(filepath.Clean("ai_handler.go"))
if err != nil {
t.Fatalf("read ai_handler.go: %v", err)
}
serviceSource, err := os.ReadFile(filepath.Clean("../ai/chat/service.go"))
if err != nil {
t.Fatalf("read chat service: %v", err)
}
typesSource, err := os.ReadFile(filepath.Clean("../ai/chat/types.go"))
if err != nil {
t.Fatalf("read chat types: %v", err)
}
handlerText := string(handlerSource)
for _, required := range []string{
"func (h *AIHandler) HandleMessages(w http.ResponseWriter, r *http.Request, sessionID string)",
"messages[i] = messages[i].ClientSafe()",
"json.NewEncoder(w).Encode(messages)",
} {
if !strings.Contains(handlerText, required) {
t.Fatalf("Assistant message-history handler must encode client-safe transcript projection: missing %q", required)
}
}
serviceText := string(serviceSource)
for _, required := range []string{
"func (s *Service) GetMessages(ctx context.Context, sessionID string) ([]Message, error)",
"messages[i] = messages[i].ClientSafe()",
} {
if !strings.Contains(serviceText, required) {
t.Fatalf("chat service message-history reads must return client-safe transcript projection: missing %q", required)
}
}
typesText := string(typesSource)
for _, required := range []string{
"func (m Message) ClientSafe() Message",
"m = m.NormalizeCollections()",
`if strings.EqualFold(m.Role, "assistant")`,
"m.Content = cleanToolCallArtifacts(m.Content)",
`m.ReasoningContent = ""`,
"return m",
} {
if !strings.Contains(typesText, required) {
t.Fatalf("chat message client-safe projection must strip hidden reasoning and raw tool-call prose: missing %q", required)
}
}
}
func TestContract_InvestigateAlertIsApprovalBound(t *testing.T) {
source, err := os.ReadFile(filepath.Clean("ai_handlers.go"))
if err != nil {
t.Fatalf("read ai_handlers.go: %v", err)
}
text := string(source)
if !strings.Contains(text, "autonomousMode := false") ||
!strings.Contains(text, "AutonomousMode: &autonomousMode") ||
!strings.Contains(text, "RequireCommandApproval: true") {
t.Fatal("investigate-alert API handoff must force request-scoped approval and command approval")
}
}
func TestContract_ProxmoxSetupScriptUsesPrivilegeSeparatedTokenACLs(t *testing.T) {
storagePerms := `
pveum aclmod /storage -user pulse-monitor@pve -role PVEDatastoreAdmin
if [ "$TOKEN_CREATED" = true ]; then
pveum aclmod /storage -token "$PULSE_TOKEN_ID" -role PVEDatastoreAdmin
fi`
pveArtifact := buildSetupScriptInstallArtifact(
"https://pulse.example",
"pve",
"https://pve.example:8006",
"https://pulse.example",
true,
"setup-token-123",
0,
)
pveScript := renderSetupScript("pve", setupScriptRenderContext{
ServerName: "pve-example",
PulseURL: "https://pulse.example",
ServerHost: "https://pve.example:8006",
SetupToken: "setup-token-123",
TokenName: "pulse-example",
TokenMatchPrefix: "pulse-example",
StoragePerms: storagePerms,
SensorsPublicKey: "ssh-ed25519 AAAATEST pulse@test",
Artifact: pveArtifact,
})
for _, required := range []string{
`TOKEN_OUTPUT=$(pveum user token add pulse-monitor@pve "$TOKEN_NAME" --privsep 1 2>&1)`,
`pveum aclmod / -user pulse-monitor@pve -role PVEAuditor`,
`pveum aclmod / -token "$PULSE_TOKEN_ID" -role PVEAuditor`,
`pveum aclmod / -user pulse-monitor@pve -role PulseMonitor`,
`pveum aclmod / -token "$PULSE_TOKEN_ID" -role PulseMonitor`,
`pveum aclmod /storage -user pulse-monitor@pve -role PVEDatastoreAdmin`,
`pveum aclmod /storage -token "$PULSE_TOKEN_ID" -role PVEDatastoreAdmin`,
} {
if !strings.Contains(pveScript, required) {
t.Fatalf("PVE setup script must contain %q", required)
}
}
if strings.Contains(pveScript, "--privsep 0") {
t.Fatal("PVE setup script must not create full-privilege API tokens")
}
pbsArtifact := buildSetupScriptInstallArtifact(
"https://pulse.example",
"pbs",
"https://pbs.example:8007",
"https://pulse.example",
false,
"setup-token-123",
0,
)
pbsScript := renderSetupScript("pbs", setupScriptRenderContext{
ServerName: "pbs-example",
PulseURL: "https://pulse.example",
ServerHost: "https://pbs.example:8007",
SetupToken: "setup-token-123",
TokenName: "pulse-example",
TokenMatchPrefix: "pulse-example",
Artifact: pbsArtifact,
})
for _, required := range []string{
`proxmox-backup-manager acl update / Audit --auth-id pulse-monitor@pbs`,
`proxmox-backup-manager acl update / Audit --auth-id "$PULSE_TOKEN_ID"`,
} {
if !strings.Contains(pbsScript, required) {
t.Fatalf("PBS setup script must contain %q", required)
}
}
for _, forbidden := range []string{
`Admin --auth-id pulse-monitor@pbs`,
`Admin --auth-id "$PULSE_TOKEN_ID"`,
} {
if strings.Contains(pbsScript, forbidden) {
t.Fatalf("PBS setup script must not grant admin ACL %q", forbidden)
}
}
}
func TestContract_HostedMagicLinkStablePrincipalProof(t *testing.T) {
source, err := os.ReadFile(filepath.Clean("magic_link_handlers.go"))
if err != nil {
t.Fatalf("read magic_link_handlers.go: %v", err)
}
text := string(source)
for _, required := range []string{
"resolveMagicLinkPrincipal",
"CreateSession(sessionToken, sessionDuration, userAgent, clientIP, principal.UserID)",
"TrackUserSession(principal.UserID, sessionToken)",
} {
if !strings.Contains(text, required) {
t.Fatalf("magic_link_handlers.go must contain %q", required)
}
}
for _, forbidden := range []string{
"CreateSession(sessionToken, sessionDuration, userAgent, clientIP, token.Email)",
"TrackUserSession(token.Email, sessionToken)",
} {
if strings.Contains(text, forbidden) {
t.Fatalf("magic_link_handlers.go must not contain legacy email-principal pattern %q", forbidden)
}
}
invariantDoc, err := os.ReadFile(filepath.Clean("../../docs/release-control/v6/internal/IDENTITY_INVARIANTS.md"))
if err != nil {
t.Fatalf("read identity invariant contract: %v", err)
}
if !strings.Contains(string(invariantDoc), "Email is contact metadata") {
t.Fatal("identity invariant contract must define email as contact metadata")
}
modelSource, err := os.ReadFile(filepath.Clean("../models/organization.go"))
if err != nil {
t.Fatalf("read organization model: %v", err)
}
if !strings.Contains(string(modelSource), "ResolvePrincipalByEmail") {
t.Fatal("organization model must expose email-to-stable-principal resolution")
}
if strings.Contains(string(modelSource), "userID = email") {
t.Fatal("organization model must not synthesize magic-link principals from email")
}
}
func TestContract_CheckoutMagicLinkDeliveryRequiresStoredPrincipalProof(t *testing.T) {
source, err := os.ReadFile(filepath.Clean("payments_webhook_handlers.go"))
if err != nil {
t.Fatalf("read payments_webhook_handlers.go: %v", err)
}
text := string(source)
for _, required := range []string{
"ResolvePrincipalByEmail(email)",
"models.IsValidOrganizationRole(role)",
"GenerateToken(email, orgID)",
"SendMagicLink(email, orgID, token, baseURL)",
} {
if !strings.Contains(text, required) {
t.Fatalf("payments_webhook_handlers.go must contain %q", required)
}
}
for _, forbidden := range []string{
"strings.EqualFold(org.OwnerEmail, email)",
"strings.EqualFold(org.OwnerUserID, email)",
"strings.EqualFold(m.Email, email)",
"strings.EqualFold(m.UserID, email)",
} {
if strings.Contains(text, forbidden) {
t.Fatalf("payments_webhook_handlers.go must not contain legacy contact-email membership pattern %q", forbidden)
}
}
identityDoc, err := os.ReadFile(filepath.Clean("../../docs/release-control/v6/internal/IDENTITY_INVARIANTS.md"))
if err != nil {
t.Fatalf("read identity invariant contract: %v", err)
}
if !strings.Contains(string(identityDoc), "Post-checkout magic-link delivery") {
t.Fatal("identity invariant contract must cover checkout magic-link delivery")
}
}
func TestContract_OrganizationRuntimeAccessUsesStrictUserIDProof(t *testing.T) {
modelSource, err := os.ReadFile(filepath.Clean("../models/organization.go"))
if err != nil {
t.Fatalf("read organization model: %v", err)
}
modelText := string(modelSource)
for _, required := range []string{
"HasMemberUserID",
"GetMemberRoleByUserID",
"IsOwnerUserID",
"CanUserIDAccess",
"CanUserIDManage",
} {
if !strings.Contains(modelText, required) {
t.Fatalf("organization model must contain strict user ID helper %q", required)
}
}
requiredByFile := map[string][]string{
"authorization.go": {
"org.CanUserIDAccess(userID)",
},
"org_handlers.go": {
"org.CanUserIDAccess(username)",
"org.CanUserIDManage(username)",
"org.IsOwnerUserID(username)",
"org.GetMemberRoleByUserID(username)",
},
"security_setup_fix.go": {
"org.CanUserIDManage(sessionUser)",
},
"cloud_org_admin_auth.go": {
"org.IsOwnerUserID(userID)",
},
}
for file, required := range requiredByFile {
source, err := os.ReadFile(filepath.Clean(file))
if err != nil {
t.Fatalf("read %s: %v", file, err)
}
text := string(source)
for _, needle := range required {
if !strings.Contains(text, needle) {
t.Fatalf("%s must contain strict runtime authorization proof %q", file, needle)
}
}
for _, forbidden := range []string{
".CanUserAccess(username)",
".CanUserAccess(userID)",
".CanUserManage(username)",
".CanUserManage(sessionUser)",
".IsOwner(username)",
".GetMemberRole(username)",
} {
if strings.Contains(text, forbidden) {
t.Fatalf("%s must not use legacy email-aware runtime accessor %q", file, forbidden)
}
}
}
identityDoc, err := os.ReadFile(filepath.Clean("../../docs/release-control/v6/internal/IDENTITY_INVARIANTS.md"))
if err != nil {
t.Fatalf("read identity invariant contract: %v", err)
}
identityText := string(identityDoc)
if !strings.Contains(identityText, "strict user-ID") || !strings.Contains(identityText, "OwnerUserID") {
t.Fatal("identity invariant contract must require strict user-ID membership checks for live authorization")
}
}
func TestContract_HostedHandoffRequiresStableSubjectProof(t *testing.T) {
directSource, err := os.ReadFile(filepath.Clean("cloud_handoff.go"))
if err != nil {
t.Fatalf("read cloud_handoff.go: %v", err)
}
exchangeSource, err := os.ReadFile(filepath.Clean("cloud_handoff_handlers.go"))
if err != nil {
t.Fatalf("read cloud_handoff_handlers.go: %v", err)
}
for file, source := range map[string]string{
"cloud_handoff.go": string(directSource),
"cloud_handoff_handlers.go": string(exchangeSource),
} {
for _, required := range []string{
"isEmailShapedHandoffUserID(userID)",
"CreateSession(sessionToken, sessionDuration, userAgent, clientIP, authz.UserID)",
"TrackUserSession(authz.UserID, sessionToken)",
} {
if !strings.Contains(source, required) {
t.Fatalf("%s must contain %q", file, required)
}
}
for _, forbidden := range []string{
"userID = email",
"email = normalizeHandoffEmail(userID)",
"CreateSession(sessionToken, sessionDuration, userAgent, clientIP, claims.Email)",
"TrackUserSession(claims.Email, sessionToken)",
} {
if strings.Contains(source, forbidden) {
t.Fatalf("%s must not contain legacy email-principal pattern %q", file, forbidden)
}
}
}
}
func TestContract_SSOStablePrincipalProof(t *testing.T) {
ssoIdentity, err := os.ReadFile(filepath.Clean("auth_principal_identity.go"))
if err != nil {
t.Fatalf("read auth_principal_identity.go: %v", err)
}
oidcSource, err := os.ReadFile(filepath.Clean("oidc_handlers.go"))
if err != nil {
t.Fatalf("read oidc_handlers.go: %v", err)
}
samlSource, err := os.ReadFile(filepath.Clean("saml_handlers.go"))
if err != nil {
t.Fatalf("read saml_handlers.go: %v", err)
}
for file, text := range map[string]string{
"auth_principal_identity.go": string(ssoIdentity),
"oidc_handlers.go": string(oidcSource),
"saml_handlers.go": string(samlSource),
} {
for _, required := range []string{
"stableSSOPrincipal",
"applySSORoleAssignments",
} {
if !strings.Contains(text, required) {
t.Fatalf("%s must contain %q", file, required)
}
}
}
for _, forbidden := range []string{
"establishOIDCSession(w, req, username, oidcTokens)",
"UpdateUserRoles(username, rolesToAssign)",
} {
if strings.Contains(string(oidcSource), forbidden) {
t.Fatalf("oidc_handlers.go must not contain legacy SSO principal pattern %q", forbidden)
}
}
for _, forbidden := range []string{
"establishSAMLSession(w, req, username, samlSession)",
"UpdateUserRoles(result.Username, rolesToAssign)",
} {
if strings.Contains(string(samlSource), forbidden) {
t.Fatalf("saml_handlers.go must not contain legacy SSO principal pattern %q", forbidden)
}
}
}
func TestContract_APITokenOwnerBindingCoversSharedMintingPaths(t *testing.T) {
files := map[string][]string{
"security_tokens.go": {
"setAPITokenOwnerUserID",
"apiTokenOwnerUserIDForRequest",
"reserved token metadata key",
},
"agent_install_command_shared.go": {
"OwnerUserID string",
"setAPITokenOwnerUserID(record, opts.OwnerUserID)",
"mergeAPITokenMetadata(record, opts.Metadata)",
},
"deploy_handlers.go": {
"setAPITokenOwnerUserID(record, ownerUserID)",
"setAPITokenOwnerUserID(runtimeRecord, apiTokenOwnerUserID(*bootstrapToken))",
"apiTokenOwnerUserIDForRequest(h.config, r)",
},
"router.go": {
"setAPITokenOwnerUserID(record, apiTokenOwnerUserIDForRequest(r.config, req))",
},
"security_setup_fix.go": {
"setAPITokenOwnerUserID(tokenRecord, setupRequest.Username)",
"setAPITokenOwnerUserID(tokenRecord, apiTokenOwnerUserIDForRequest(r.config, rq))",
},
}
for file, needles := range files {
source, err := os.ReadFile(filepath.Clean(file))
if err != nil {
t.Fatalf("read %s: %v", file, err)
}
text := string(source)
for _, needle := range needles {
if !strings.Contains(text, needle) {
t.Fatalf("%s must contain %q", file, needle)
}
}
}
}
func TestContractAISettingsClampsPaidRuntimeControlsToEntitlements(t *testing.T) {
t.Parallel()
tmp := t.TempDir()
cfg := &config.Config{DataPath: tmp}
persistence := config.NewConfigPersistence(tmp)
aiCfg := config.NewDefaultAIConfig()
aiCfg.Enabled = true
aiCfg.Model = "ollama:llama3"
aiCfg.OllamaBaseURL = "http://127.0.0.1:11434"
aiCfg.ControlLevel = config.ControlLevelAutonomous
aiCfg.PatrolAutoFix = true
aiCfg.AlertTriggeredAnalysis = true
if err := persistence.SaveAIConfig(*aiCfg); err != nil {
t.Fatalf("save ai config: %v", err)
}
handler := newTestAISettingsHandler(cfg, persistence, nil)
handler.defaultAIService.SetLicenseChecker(stubLicenseChecker{allow: false})
req := newLoopbackRequest(http.MethodGet, "/api/settings/ai", nil)
rec := httptest.NewRecorder()
handler.HandleGetAISettings(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("GET /api/settings/ai status = %d, body %s", rec.Code, rec.Body.String())
}
var resp AISettingsResponse
if err := json.Unmarshal(rec.Body.Bytes(), &resp); err != nil {
t.Fatalf("decode response: %v", err)
}
if resp.ControlLevel != config.ControlLevelControlled {
t.Fatalf("control level = %q, want %q", resp.ControlLevel, config.ControlLevelControlled)
}
if resp.PatrolAutoFix {
t.Fatal("patrol auto-remediation must be entitlement-clamped")
}
if resp.AlertTriggeredAnalysis {
t.Fatal("alert-triggered analysis must be entitlement-clamped")
}
}
func TestContractAnthropicOAuthSetupEndpointsFailClosedPayload(t *testing.T) {
handler := &AISettingsHandler{}
tests := []struct {
name string
method string
path string
body string
call func(http.ResponseWriter, *http.Request)
}{
{
name: "start",
method: http.MethodGet,
path: "/api/ai/oauth/start",
call: handler.HandleOAuthStart,
},
{
name: "exchange",
method: http.MethodPost,
path: "/api/ai/oauth/exchange",
body: `{"code":"code","state":"state"}`,
call: handler.HandleOAuthExchange,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
req := newLoopbackRequest(tt.method, tt.path, strings.NewReader(tt.body))
rec := httptest.NewRecorder()
tt.call(rec, req)
if rec.Code != http.StatusNotImplemented {
t.Fatalf("status=%d, want %d: %s", rec.Code, http.StatusNotImplemented, rec.Body.String())
}
var resp struct {
Success bool `json:"success"`
Error string `json:"error"`
Message string `json:"message"`
}
if err := json.Unmarshal(rec.Body.Bytes(), &resp); err != nil {
t.Fatalf("decode response: %v", err)
}
if resp.Success {
t.Fatal("unsupported OAuth response must not report success")
}
if resp.Error != "unsupported_anthropic_oauth" {
t.Fatalf("error=%q, want unsupported_anthropic_oauth", resp.Error)
}
if !strings.Contains(resp.Message, "Configure an Anthropic API key") {
t.Fatalf("message must direct operators to API-key setup, got %q", resp.Message)
}
})
}
}
func sortedVMChartKeys(values map[string]VMChartData) []string {
keys := make([]string, 0, len(values))
for key := range values {
keys = append(keys, key)
}
sort.Strings(keys)
return keys
}
type contractCapturingStreamingProvider struct {
lastRequest providers.ChatRequest
}
type contractSupplementalUsageProvider struct {
settled bool
readyAt time.Time
records []unifiedresources.IngestRecord
}
func (p *contractSupplementalUsageProvider) SupplementalRecords(*monitoring.Monitor, string) []unifiedresources.IngestRecord {
out := make([]unifiedresources.IngestRecord, len(p.records))
copy(out, p.records)
return out
}
func (p *contractSupplementalUsageProvider) SnapshotOwnedSources() []unifiedresources.DataSource {
return []unifiedresources.DataSource{unifiedresources.SourceTrueNAS}
}
func (p *contractSupplementalUsageProvider) SupplementalInventoryReadyAt(*monitoring.Monitor, string) (time.Time, bool) {
return p.readyAt, p.settled
}
func (p *contractSupplementalUsageProvider) settle(count int) {
now := time.Now().UTC()
p.readyAt = now
p.settled = true
p.records = make([]unifiedresources.IngestRecord, 0, count)
for i := 0; i < count; i++ {
host := fmt.Sprintf("contract-truenas-%02d.lab.local", i+1)
p.records = append(p.records, unifiedresources.IngestRecord{
SourceID: fmt.Sprintf("system:contract-truenas-%02d", i+1),
Resource: unifiedresources.Resource{
ID: fmt.Sprintf("contract-truenas-%02d", i+1),
Type: unifiedresources.ResourceTypeAgent,
Name: host,
Status: unifiedresources.StatusOnline,
LastSeen: now,
UpdatedAt: now,
Identity: unifiedresources.ResourceIdentity{
Hostnames: []string{host},
},
TrueNAS: &unifiedresources.TrueNASData{
Hostname: host,
},
},
})
}
}
func (p *contractCapturingStreamingProvider) Chat(ctx context.Context, req providers.ChatRequest) (*providers.ChatResponse, error) {
p.lastRequest = req
return &providers.ChatResponse{Content: "ok", Model: req.Model}, nil
}
func (p *contractCapturingStreamingProvider) ChatStream(ctx context.Context, req providers.ChatRequest, callback providers.StreamCallback) error {
p.lastRequest = req
callback(providers.StreamEvent{Type: "content", Data: providers.ContentEvent{Text: "hello"}})
callback(providers.StreamEvent{Type: "done", Data: providers.DoneEvent{StopReason: "end_turn"}})
return nil
}
func (p *contractCapturingStreamingProvider) TestConnection(ctx context.Context) error { return nil }
func (p *contractCapturingStreamingProvider) Name() string { return "contract-capture" }
func (p *contractCapturingStreamingProvider) ListModels(ctx context.Context) ([]providers.ModelInfo, error) {
return nil, nil
}
func (p *contractCapturingStreamingProvider) SupportsThinking(model string) bool { return false }
func TestContract_WebSocketTrustedProxyHostedOrigin(t *testing.T) {
t.Setenv("PULSE_TRUSTED_PROXY_CIDRS", "127.0.0.1/32")
resetTrustedProxyCIDRsForTests()
rawToken := "contract-ws-origin-forwarded-123.12345678"
record := newTokenRecord(t, rawToken, []string{config.ScopeMonitoringRead}, nil)
server, cleanup := newWebSocketRouter(t, []string{}, record)
defer cleanup()
wsURL := "ws" + strings.TrimPrefix(server.URL, "http") + "/ws?org_id=default"
headers := http.Header{}
headers.Set("X-API-Token", rawToken)
headers.Set("Origin", "https://tenant.example.com")
headers.Set("X-Forwarded-Proto", "https")
headers.Set("X-Forwarded-Host", "tenant.example.com")
conn, resp, err := websocket.DefaultDialer.Dial(wsURL, headers)
if err != nil {
t.Fatalf("expected websocket connection behind trusted proxy, got %v", err)
}
if resp == nil || resp.StatusCode != http.StatusSwitchingProtocols {
conn.Close()
t.Fatalf("expected 101 switching protocols, got %v", resp)
}
conn.Close()
}
func TestContract_AIRelayTargetHostnameEquivalence(t *testing.T) {
server := agentexec.NewServer(func(string, string, string) bool { return true })
ts := newIPv4HTTPServer(t, http.HandlerFunc(server.HandleWebSocket))
defer ts.Close()
conn := registerAgent(t, ts.URL, "agent-1", "prox97.seftic.local")
defer conn.Close()
adapter := &agentCommandAdapter{handler: &AISettingsHandler{agentServer: server}}
if got := adapter.FindAgentForTarget("prox97"); got != "agent-1" {
t.Fatalf("FindAgentForTarget(%q) = %q, want agent-1", "prox97", got)
}
if got := adapter.FindAgentForTarget("prox97.seftic.local"); got != "agent-1" {
t.Fatalf("FindAgentForTarget(%q) = %q, want agent-1", "prox97.seftic.local", got)
}
if got := adapter.FindAgentForTarget("prox97.other.local"); got != "" {
t.Fatalf("FindAgentForTarget(%q) = %q, want empty for distinct FQDN", "prox97.other.local", got)
}
}
func TestContract_WireAIChatDependencies_WiresTrueNASAppActionProvider(t *testing.T) {
router := &Router{
trueNASPoller: monitoring.NewTrueNASPoller(nil, 0, nil),
}
service := &capturingAIService{}
router.wireAIChatDependenciesForService(context.Background(), service)
if service.appContainerActionProvider == nil {
t.Fatal("expected TrueNAS app action provider to be wired into AI chat dependencies")
}
if service.appContainerReadProvider == nil {
t.Fatal("expected TrueNAS app read provider to be wired into AI chat dependencies")
}
if service.appContainerConfigProvider == nil {
t.Fatal("expected TrueNAS app config provider to be wired into AI chat dependencies")
}
}
func TestContract_AITransportResourceTypeUsesSharedActionResourceVocabulary(t *testing.T) {
for _, input := range []string{"truenas", "SYSTEM-CONTAINER", "app-container", "host"} {
got := normalizeAITransportResourceType(input)
want := agentcapabilities.NormalizeActionResourceType(input)
if got != want {
t.Fatalf("normalizeAITransportResourceType(%q) = %q, want shared action resource type %q", input, got, want)
}
}
if got := normalizeAITransportResourceType("truenas"); got != agentcapabilities.ActionTargetTypeAgent {
t.Fatalf("truenas resource type = %q, want %q", got, agentcapabilities.ActionTargetTypeAgent)
}
}
func TestContract_ChatServiceAdapterPatrolForwardsExecutionID(t *testing.T) {
cfg := chat.Config{
DataDir: t.TempDir(),
AIConfig: &config.AIConfig{
Enabled: true,
ChatModel: "stub:model",
PatrolModel: "stub:model",
},
}
service := chat.NewService(cfg)
provider := &contractCapturingStreamingProvider{}
setUnexportedField(t, service, "providerFactory", func(string) (providers.StreamingProvider, error) {
return provider, nil
})
if err := service.Start(context.Background()); err != nil {
t.Fatalf("Start: %v", err)
}
t.Cleanup(func() { _ = service.Stop(context.Background()) })
adapter := &chatServiceAdapter{svc: service}
resp, err := adapter.ExecutePatrolStream(context.Background(), ai.PatrolExecuteRequest{
Prompt: "patrol",
ExecutionID: "patrol-run-contract",
}, func(ai.ChatStreamEvent) {})
if err != nil {
t.Fatalf("ExecutePatrolStream: %v", err)
}
if resp == nil || resp.Content == "" {
t.Fatalf("expected patrol response content, got %#v", resp)
}
if provider.lastRequest.ExecutionID != "patrol-run-contract" {
t.Fatalf("execution_id=%q want patrol-run-contract", provider.lastRequest.ExecutionID)
}
}
func TestContract_AIQuickstartPayloadFieldsAreRetired(t *testing.T) {
settingsBody, err := json.Marshal(AISettingsResponse{
Enabled: true,
Configured: false,
})
if err != nil {
t.Fatalf("marshal AI settings response: %v", err)
}
if bytes.Contains(settingsBody, []byte(`quickstart`)) {
t.Fatalf("expected AI settings payload not to expose retired quickstart fields, got %s", settingsBody)
}
statusBody, err := json.Marshal(PatrolStatusResponse{
RuntimeState: ai.PatrolRuntimeStateActive,
Healthy: true,
})
if err != nil {
t.Fatalf("marshal patrol status response: %v", err)
}
if bytes.Contains(statusBody, []byte(`quickstart`)) {
t.Fatalf("expected patrol status payload not to expose retired quickstart fields, got %s", statusBody)
}
}
func TestContract_AISettingsUpdateRequiresProviderBeforeEnable(t *testing.T) {
tmp := t.TempDir()
cfg := &config.Config{DataPath: tmp}
persistence := config.NewConfigPersistence(tmp)
handler := newTestAISettingsHandler(cfg, persistence, nil)
req := newLoopbackRequest(http.MethodPut, "/api/settings/ai/update", strings.NewReader(`{
"enabled": true
}`))
req.Header.Set("Content-Type", "application/json")
rec := httptest.NewRecorder()
handler.HandleUpdateAISettings(rec, req)
if rec.Code != http.StatusBadRequest {
t.Fatalf("status=%d, want %d: %s", rec.Code, http.StatusBadRequest, rec.Body.String())
}
if strings.Contains(strings.ToLower(rec.Body.String()), "quickstart") {
t.Fatalf("provider-required response must not mention retired quickstart, got %s", rec.Body.String())
}
if !strings.Contains(rec.Body.String(), "Please configure a provider") {
t.Fatalf("expected provider-required response, got %s", rec.Body.String())
}
}
func TestContract_AISettingsUpdateProviderResolutionJSONSnapshot(t *testing.T) {
ollama := newIPv4HTTPServer(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
switch r.URL.Path {
case "/api/tags":
_ = json.NewEncoder(w).Encode(map[string]any{
"models": []map[string]any{
{"name": "llama3:latest"},
{"name": "mistral:latest"},
},
})
default:
http.NotFound(w, r)
}
}))
defer ollama.Close()
tmp := t.TempDir()
cfg := &config.Config{DataPath: tmp}
persistence := config.NewConfigPersistence(tmp)
handler := newTestAISettingsHandler(cfg, persistence, nil)
req := newLoopbackRequest(http.MethodPut, "/api/settings/ai/update", strings.NewReader(fmt.Sprintf(`{
"enabled": true,
"ollama_base_url": %q
}`, ollama.URL)))
req.Header.Set("Content-Type", "application/json")
rec := httptest.NewRecorder()
handler.HandleUpdateAISettings(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("status=%d, want %d: %s", rec.Code, http.StatusOK, rec.Body.String())
}
want := fmt.Sprintf(`{
"enabled":true,
"model":"ollama:llama3:latest",
"configured":true,
"custom_context":"",
"auth_method":"api_key",
"oauth_connected":false,
"patrol_interval_minutes":360,
"patrol_enabled":true,
"patrol_auto_fix":false,
"alert_triggered_analysis":true,
"patrol_event_triggers_enabled":true,
"patrol_alert_triggers_enabled":true,
"patrol_anomaly_triggers_enabled":true,
"patrol_alert_trigger_min_severity":"critical",
"patrol_alert_trigger_types":[],
"use_proactive_thresholds":false,
"available_models":[],
"anthropic_configured":false,
"openai_configured":false,
"openrouter_configured":false,
"deepseek_configured":false,
"gemini_configured":false,
"zai_configured":false,
"groq_configured":false,
"mistral_configured":false,
"cerebras_configured":false,
"together_configured":false,
"fireworks_configured":false,
"ollama_configured":true,
"ollama_base_url":%q,
"ollama_password_set":false,
"ollama_keep_alive":"30s",
"configured_providers":["ollama"],
"control_level":"read_only",
"protected_guests":[],
"discovery_enabled":false,
"patrol_readiness":{
"status":"warning",
"ready":true,
"cause":"model_tool_support_unverified",
"summary":"Ollama connectivity alone does not prove tool support. Use an Ollama model that returns tool_calls for Patrol verification.",
"provider":"ollama",
"model":"ollama:llama3:latest",
"checks":[{
"id":"configuration",
"status":"warning",
"cause":"model_tool_support_unverified",
"label":"Patrol control",
"message":"Ollama connectivity alone does not prove tool support. Use an Ollama model that returns tool_calls for Patrol verification."
}]
}
}`, ollama.URL)
// patrol_preflight is now emitted on update responses with dynamic
// timestamps and durations that vary per run; strip it from the
// snapshot comparison and rely on dedicated preflight tests for its
// shape.
assertJSONSnapshot(t, rec.Body.Bytes(), want, "patrol_preflight", "providers")
}
func TestContract_PatrolStatusDoesNotSurfaceQuickstartActivation(t *testing.T) {
handler, patrol, _, _ := setupAIHandlerWithPatrol(t)
persistence := handler.defaultPersistence
aiCfg := config.NewDefaultAIConfig()
aiCfg.Enabled = true
if err := persistence.SaveAIConfig(*aiCfg); err != nil {
t.Fatalf("SaveAIConfig: %v", err)
}
if err := handler.defaultAIService.LoadConfig(); err != nil {
t.Fatalf("LoadConfig: %v", err)
}
setUnexportedField(t, patrol, "lastBlockedReason", "Quickstart credits exhausted. Connect your API key to continue using AI Patrol.")
setUnexportedField(t, patrol, "lastBlockedAt", time.Now().UTC())
req := httptest.NewRequest(http.MethodGet, "/api/ai/patrol/status", nil)
rec := httptest.NewRecorder()
handler.HandleGetPatrolStatus(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("status=%d, want %d: %s", rec.Code, http.StatusOK, rec.Body.String())
}
var resp PatrolStatusResponse
if err := json.Unmarshal(rec.Body.Bytes(), &resp); err != nil {
t.Fatalf("decode patrol status: %v", err)
}
if resp.RuntimeState == ai.PatrolRuntimeStateBlocked {
t.Fatalf("runtime_state=%q, want non-blocked for retired quickstart", resp.RuntimeState)
}
if !resp.Enabled {
t.Fatal("expected patrol status to remain enabled while activation is required")
}
if !resp.Healthy {
t.Fatal("expected retired quickstart activation state to stay healthy on the public status surface")
}
if strings.Contains(strings.ToLower(resp.BlockedReason), "quickstart") {
t.Fatalf("blocked_reason must not expose retired quickstart copy, got %q", resp.BlockedReason)
}
}
func TestContract_PatrolStatusMissingAssistantServiceUsesNativeSurfaceIdentity(t *testing.T) {
handler := &AISettingsHandler{}
req := httptest.NewRequest(http.MethodGet, "/api/ai/patrol/status", nil)
rec := httptest.NewRecorder()
handler.HandleGetPatrolStatus(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("status=%d, want %d: %s", rec.Code, http.StatusOK, rec.Body.String())
}
var resp PatrolStatusResponse
if err := json.Unmarshal(rec.Body.Bytes(), &resp); err != nil {
t.Fatalf("decode patrol status: %v", err)
}
if resp.Readiness == nil {
t.Fatal("expected readiness payload when Assistant service is missing")
}
if resp.Readiness.Summary != "Pulse Assistant runtime service is not available." {
t.Fatalf("readiness summary = %q", resp.Readiness.Summary)
}
var serviceCheck *PatrolReadinessCheck
for i := range resp.Readiness.Checks {
if resp.Readiness.Checks[i].ID == "service" {
serviceCheck = &resp.Readiness.Checks[i]
break
}
}
if serviceCheck == nil {
t.Fatalf("service readiness check missing from %+v", resp.Readiness.Checks)
}
if serviceCheck.Label != "Assistant runtime service" {
t.Fatalf("service readiness label = %q", serviceCheck.Label)
}
if serviceCheck.Message != "Pulse Assistant runtime service is not available." {
t.Fatalf("service readiness message = %q", serviceCheck.Message)
}
if strings.Contains(rec.Body.String(), "Pulse AI") {
t.Fatalf("patrol status payload must not expose legacy Pulse AI runtime identity: %s", rec.Body.String())
}
}
func TestContract_AISettingsDoesNotExposeQuickstartActivation(t *testing.T) {
tmp := t.TempDir()
cfg := &config.Config{DataPath: tmp}
persistence := config.NewConfigPersistence(tmp)
handler := newTestAISettingsHandler(cfg, persistence, nil)
req := httptest.NewRequest(http.MethodGet, "/api/settings/ai", nil)
rec := httptest.NewRecorder()
handler.HandleGetAISettings(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("status=%d, want %d: %s", rec.Code, http.StatusOK, rec.Body.String())
}
var resp AISettingsResponse
if err := json.Unmarshal(rec.Body.Bytes(), &resp); err != nil {
t.Fatalf("decode ai settings: %v", err)
}
if resp.Configured {
t.Fatal("expected AI settings to remain unconfigured without BYOK/local provider")
}
if bytes.Contains(bytes.ToLower(rec.Body.Bytes()), []byte("quickstart")) {
t.Fatalf("AI settings payload must not expose retired quickstart fields, got %s", rec.Body.Bytes())
}
}
func TestContract_AISettingsBYOKOverrideDoesNotExposeQuickstartInventoryJSONSnapshot(t *testing.T) {
tmp := t.TempDir()
cfg := &config.Config{DataPath: tmp}
persistence := config.NewConfigPersistence(tmp)
aiCfg := config.NewDefaultAIConfig()
aiCfg.Enabled = true
aiCfg.Model = "openai:gpt-4o"
aiCfg.OpenAIAPIKey = "sk-openai-test"
if err := persistence.SaveAIConfig(*aiCfg); err != nil {
t.Fatalf("SaveAIConfig: %v", err)
}
handler := newTestAISettingsHandler(cfg, persistence, nil)
req := httptest.NewRequest(http.MethodGet, "/api/settings/ai", nil)
rec := httptest.NewRecorder()
handler.HandleGetAISettings(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("status=%d, want %d: %s", rec.Code, http.StatusOK, rec.Body.String())
}
const want = `{
"enabled":true,
"model":"openai:gpt-4o",
"configured":true,
"custom_context":"",
"auth_method":"api_key",
"oauth_connected":false,
"patrol_interval_minutes":360,
"patrol_enabled":true,
"patrol_auto_fix":false,
"alert_triggered_analysis":true,
"patrol_event_triggers_enabled":true,
"patrol_alert_triggers_enabled":true,
"patrol_anomaly_triggers_enabled":true,
"patrol_alert_trigger_min_severity":"critical",
"patrol_alert_trigger_types":[],
"use_proactive_thresholds":false,
"available_models":[],
"anthropic_configured":false,
"openai_configured":true,
"openrouter_configured":false,
"deepseek_configured":false,
"gemini_configured":false,
"zai_configured":false,
"groq_configured":false,
"mistral_configured":false,
"cerebras_configured":false,
"together_configured":false,
"fireworks_configured":false,
"ollama_configured":false,
"ollama_base_url":"http://localhost:11434",
"ollama_password_set":false,
"ollama_keep_alive":"30s",
"configured_providers":["openai"],
"control_level":"read_only",
"protected_guests":[],
"discovery_enabled":false,
"patrol_readiness":{"status":"not_ready","ready":false,"cause":"service_unavailable","summary":"Pulse Patrol service is not available.","checks":[{"id":"service","status":"not_ready","cause":"service_unavailable","label":"Patrol service","message":"Pulse Patrol service is not available.","action":"restart_service"}]}
}`
assertJSONSnapshot(t, rec.Body.Bytes(), want, "providers")
}
func TestContract_AISettingsProviderRegistryMetadata(t *testing.T) {
settings := config.NewDefaultAIConfig()
settings.OpenAIAPIKey = "sk-test"
settings.GroqAPIKey = "gsk-test"
settings.OllamaBaseURL = config.DefaultOllamaBaseURL
defs := aiProviderDefinitionResponses(settings)
gotIDs := make([]string, 0, len(defs))
byID := make(map[string]AIProviderDefinitionResponse, len(defs))
for _, def := range defs {
gotIDs = append(gotIDs, def.ID)
byID[def.ID] = def
}
wantIDs := []string{
"anthropic",
"openai",
"openrouter",
"deepseek",
"gemini",
"zai",
"groq",
"mistral",
"cerebras",
"together",
"fireworks",
"ollama",
}
if !reflect.DeepEqual(gotIDs, wantIDs) {
t.Fatalf("provider ids = %#v, want %#v", gotIDs, wantIDs)
}
if _, ok := byID["quickstart"]; ok {
t.Fatal("retired quickstart provider must not be user-configurable")
}
cases := map[string]struct {
displayName string
apiKeyField string
clearKeyField string
configuredField string
defaultBaseURL string
modelsDevProviderID string
configured bool
}{
"zai": {
displayName: "Z.ai",
apiKeyField: "zai_api_key",
clearKeyField: "clear_zai_key",
configuredField: "zai_configured",
defaultBaseURL: "https://api.z.ai/api/paas/v4",
modelsDevProviderID: "zai",
},
"groq": {
displayName: "Groq",
apiKeyField: "groq_api_key",
clearKeyField: "clear_groq_key",
configuredField: "groq_configured",
defaultBaseURL: "https://api.groq.com/openai/v1",
modelsDevProviderID: "groq",
configured: true,
},
"mistral": {
displayName: "Mistral",
apiKeyField: "mistral_api_key",
clearKeyField: "clear_mistral_key",
configuredField: "mistral_configured",
defaultBaseURL: "https://api.mistral.ai/v1",
modelsDevProviderID: "mistral",
},
"cerebras": {
displayName: "Cerebras",
apiKeyField: "cerebras_api_key",
clearKeyField: "clear_cerebras_key",
configuredField: "cerebras_configured",
defaultBaseURL: "https://api.cerebras.ai/v1",
modelsDevProviderID: "cerebras",
},
"together": {
displayName: "Together AI",
apiKeyField: "together_api_key",
clearKeyField: "clear_together_key",
configuredField: "together_configured",
defaultBaseURL: "https://api.together.xyz/v1",
modelsDevProviderID: "togetherai",
},
"fireworks": {
displayName: "Fireworks AI",
apiKeyField: "fireworks_api_key",
clearKeyField: "clear_fireworks_key",
configuredField: "fireworks_configured",
defaultBaseURL: "https://api.fireworks.ai/inference/v1",
modelsDevProviderID: "fireworks-ai",
},
}
for id, want := range cases {
got, ok := byID[id]
if !ok {
t.Fatalf("provider %q missing from registry response", id)
}
if got.DisplayName != want.displayName ||
got.Protocol != string(config.AIProviderProtocolOpenAICompatible) ||
got.APIKeyField != want.apiKeyField ||
got.ClearKeyField != want.clearKeyField ||
got.ConfiguredField != want.configuredField ||
got.DefaultBaseURL != want.defaultBaseURL ||
got.ModelsDevProviderID != want.modelsDevProviderID ||
!got.RequiresAPIKey ||
!got.UserConfigurable ||
got.Configured != want.configured {
t.Fatalf("provider %s metadata drifted: %#v", id, got)
}
}
if !byID["openai"].Configured {
t.Fatal("OpenAI provider metadata should reflect configured API key")
}
if byID["ollama"].RequiresAPIKey {
t.Fatal("Ollama provider metadata must not require an API key")
}
if !byID["ollama"].Configured {
t.Fatal("Ollama provider metadata should reflect the default local endpoint")
}
}
func TestContract_ChartMetricPointsPreserveMillisecondPrecision(t *testing.T) {
pointTime := time.Date(2026, time.March, 31, 12, 0, 0, 987_000_000, time.UTC)
converted := monitorPointsToAPI([]monitoring.MetricPoint{{
Timestamp: pointTime,
Value: 42,
}})
if len(converted) != 1 {
t.Fatalf("expected one converted point, got %d", len(converted))
}
if converted[0].Timestamp != pointTime.UnixMilli() {
t.Fatalf("expected millisecond timestamp %d, got %d", pointTime.UnixMilli(), converted[0].Timestamp)
}
}
func TestContract_StorageChartsUseCanonicalMetricsTargetIDs(t *testing.T) {
monitor, state, metricsHistory := newTestMonitor(t)
now := time.Now().UTC().Add(-5 * time.Minute).Truncate(time.Millisecond)
metricsHistory.AddStorageMetric("vc-1:datastore:datastore-202", "usage", 0.25, now)
metricsHistory.AddStorageMetric("vc-1:datastore:datastore-202", "used", 3.57*1024*1024*1024*1024, now)
metricsHistory.AddStorageMetric("vc-1:datastore:datastore-202", "avail", 11.03*1024*1024*1024*1024, now)
metricsHistory.AddStorageMetric("pool:archive", "usage", 0.36, now)
metricsHistory.AddStorageMetric("pool:archive", "used", 5.49*1024*1024*1024*1024, now)
metricsHistory.AddStorageMetric("pool:archive", "avail", 4.51*1024*1024*1024*1024, now)
adapter := unifiedresources.NewMonitorAdapter(nil)
adapter.PopulateSnapshotAndSupplemental(state.GetSnapshot(), map[unifiedresources.DataSource][]unifiedresources.IngestRecord{
unifiedresources.SourceVMware: {
{
SourceID: "vc-1:datastore:datastore-202",
Resource: unifiedresources.Resource{
ID: "storage-vmware-1",
Type: unifiedresources.ResourceTypeStorage,
Name: "archive-tier",
Status: unifiedresources.StatusOnline,
LastSeen: now,
Storage: &unifiedresources.StorageMeta{
Type: "datastore",
Platform: "vmware",
Nodes: []string{"esxi-01.lab.local"},
},
VMware: &unifiedresources.VMwareData{
ConnectionID: "vc-1",
EntityType: "datastore",
ManagedObjectID: "datastore-202",
RuntimeHostName: "esxi-01.lab.local",
},
},
},
},
unifiedresources.SourceTrueNAS: {
{
SourceID: "pool:archive",
Resource: unifiedresources.Resource{
ID: "storage-truenas-1",
Type: unifiedresources.ResourceTypeStorage,
Name: "archive",
Status: unifiedresources.StatusOnline,
LastSeen: now,
Storage: &unifiedresources.StorageMeta{
Type: "zfs-pool",
Platform: "truenas",
},
TrueNAS: &unifiedresources.TrueNASData{
Hostname: "truenas-main",
},
},
},
},
})
setTestUnexportedField(t, monitor, "resourceStore", monitoring.ResourceStoreInterface(adapter))
router := &Router{monitor: monitor}
req := httptest.NewRequest(http.MethodGet, "/api/storage-charts?range=60", nil)
rec := httptest.NewRecorder()
router.handleStorageCharts(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("expected 200, got %d: %s", rec.Code, rec.Body.String())
}
var decoded StorageChartsResponse
if err := json.Unmarshal(rec.Body.Bytes(), &decoded); err != nil {
t.Fatalf("unmarshal storage charts response: %v", err)
}
if _, ok := decoded.Pools["vc-1:datastore:datastore-202"]; !ok {
t.Fatalf("expected VMware datastore chart keyed by canonical metrics target, got %v", decoded.Pools)
}
if _, ok := decoded.Pools["pool:archive"]; !ok {
t.Fatalf("expected TrueNAS pool chart keyed by canonical metrics target, got %v", decoded.Pools)
}
if _, ok := decoded.Pools["storage-vmware-1"]; ok {
t.Fatalf("expected raw VMware resource id to stay out of chart payload, got %v", decoded.Pools)
}
if _, ok := decoded.Pools["storage-truenas-1"]; ok {
t.Fatalf("expected raw TrueNAS resource id to stay out of chart payload, got %v", decoded.Pools)
}
vmwarePool := decoded.Pools["vc-1:datastore:datastore-202"]
if len(vmwarePool.Used) != 1 || len(vmwarePool.Avail) != 1 {
t.Fatalf("expected canonical storage summary payload to preserve used/avail series, got %+v", vmwarePool)
}
if vmwarePool.Used[0].Timestamp != now.UnixMilli() || vmwarePool.Avail[0].Timestamp != now.UnixMilli() {
t.Fatalf(
"expected storage summary series to preserve millisecond timestamps, got used=%v avail=%v",
vmwarePool.Used,
vmwarePool.Avail,
)
}
}
func TestContract_StorageSummaryChartsStayAggregateOnly(t *testing.T) {
monitor, state, metricsHistory := newTestMonitor(t)
now := time.Now().UTC().Truncate(time.Second)
state.Storage = []models.Storage{
{ID: "store-1", Name: "Store One"},
{ID: "store-2", Name: "Store Two"},
}
metricsHistory.AddStorageMetric("store-1", "used", 400, now)
metricsHistory.AddStorageMetric("store-1", "avail", 600, now)
metricsHistory.AddStorageMetric("store-1", "total", 1000, now)
metricsHistory.AddStorageMetric("store-2", "used", 100, now)
metricsHistory.AddStorageMetric("store-2", "avail", 900, now)
metricsHistory.AddStorageMetric("store-2", "total", 1000, now)
syncTestResourceStore(t, monitor, state)
router := &Router{monitor: monitor}
req := httptest.NewRequest(http.MethodGet, "/api/charts/storage-summary?range=24h", nil)
rec := httptest.NewRecorder()
router.handleStorageSummaryCharts(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("expected 200, got %d: %s", rec.Code, rec.Body.String())
}
var decoded StorageSummaryTrendResponse
if err := json.Unmarshal(rec.Body.Bytes(), &decoded); err != nil {
t.Fatalf("unmarshal storage summary response: %v", err)
}
if len(decoded.Capacity) != 1 {
t.Fatalf("expected one aggregate capacity point, got %+v", decoded.Capacity)
}
if decoded.Capacity[0].Value != 25 {
t.Fatalf("expected aggregate capacity of 25%%, got %+v", decoded.Capacity[0])
}
if decoded.Stats.PointCounts.Total != 1 || decoded.Stats.PointCounts.Storage != 1 {
t.Fatalf("expected aggregate-only point counts, got %+v", decoded.Stats.PointCounts)
}
}
func TestContract_TrueNASConnectionsDisabledMessageIsExplicit(t *testing.T) {
setTrueNASFeatureForTest(t, false)
handler, _, _ := newTrueNASHandlersForTest(t, nil)
req := httptest.NewRequest(http.MethodGet, "/api/truenas/connections", nil)
rec := httptest.NewRecorder()
handler.HandleList(rec, req)
if rec.Code != http.StatusNotFound {
t.Fatalf("expected 404 when TrueNAS integration is disabled, got %d: %s", rec.Code, rec.Body.String())
}
if !strings.Contains(rec.Body.String(), "explicitly disabled") {
t.Fatalf("expected explicit disable message, got %s", rec.Body.String())
}
}
func TestContract_TrueNASSavedConnectionTestsUpdateRuntimeSummary(t *testing.T) {
setTrueNASFeatureForTest(t, true)
connection := config.TrueNASInstance{
ID: "conn-1",
Name: "tower",
Host: "truenas.local",
Port: 443,
APIKey: "super-secret",
UseHTTPS: true,
InsecureSkipVerify: false,
Enabled: true,
}
handler, persistence, _ := newTrueNASHandlersForTest(t, nil)
if err := persistence.SaveTrueNASConfig([]config.TrueNASInstance{connection}); err != nil {
t.Fatalf("seed truenas config: %v", err)
}
poller := monitoring.NewTrueNASPoller(nil, time.Minute, nil)
handler.getPoller = func(context.Context) *monitoring.TrueNASPoller { return poller }
handler.newClient = func(cfg truenas.ClientConfig) (trueNASClient, error) {
return &fakeTrueNASClient{}, nil
}
req := httptest.NewRequest(http.MethodPost, "/api/truenas/connections/conn-1/test", nil)
rec := httptest.NewRecorder()
handler.HandleTestSavedConnection(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("expected 200, got %d: %s", rec.Code, rec.Body.String())
}
summary := poller.ConnectionSummaries("default", []config.TrueNASInstance{connection})[connection.ID]
if summary.Poll == nil || summary.Poll.LastSuccessAt == nil {
t.Fatalf("expected saved manual test to refresh poll summary, got %+v", summary.Poll)
}
}
func TestContract_VMwareConnectionsDisabledMessageIsExplicit(t *testing.T) {
setVMwareFeatureForTest(t, false)
handler, _ := newVMwareHandlersForTest(t)
req := httptest.NewRequest(http.MethodGet, "/api/vmware/connections", nil)
rec := httptest.NewRecorder()
handler.HandleList(rec, req)
if rec.Code != http.StatusNotFound {
t.Fatalf("expected 404 when VMware integration is disabled, got %d: %s", rec.Code, rec.Body.String())
}
if !strings.Contains(rec.Body.String(), "explicitly disabled") {
t.Fatalf("expected explicit disable message, got %s", rec.Body.String())
}
}
func TestContract_VMwareSavedConnectionTestsUpdateRuntimeSummary(t *testing.T) {
setVMwareFeatureForTest(t, true)
connection := config.VMwareVCenterInstance{
ID: "conn-1",
Name: "lab-vcenter",
Host: "vcsa.lab.local",
Port: 443,
Username: "administrator@vsphere.local",
Password: "super-secret",
Enabled: true,
}
handler, persistence := newVMwareHandlersForTest(t)
poller := monitoring.NewVMwarePoller(nil, time.Minute)
handler.getPoller = func(context.Context) *monitoring.VMwarePoller { return poller }
if err := persistence.SaveVMwareConfig([]config.VMwareVCenterInstance{connection}); err != nil {
t.Fatalf("seed vmware config: %v", err)
}
handler.newClient = func(cfg vmware.ClientConfig) (vmwareClient, error) {
return &fakeVMwareClient{
testConnection: func(context.Context) (*vmware.InventorySummary, error) {
return &vmware.InventorySummary{Hosts: 3, VMs: 20, Datastores: 4, Networks: 6, VIRelease: "8.0.3"}, nil
},
}, nil
}
req := httptest.NewRequest(http.MethodPost, "/api/vmware/connections/conn-1/test", nil)
rec := httptest.NewRecorder()
handler.HandleTestSavedConnection(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("expected 200, got %d: %s", rec.Code, rec.Body.String())
}
summary := poller.ConnectionSummaries("default", []config.VMwareVCenterInstance{connection})[connection.ID]
if summary.Poll == nil || summary.Poll.LastSuccessAt == nil {
t.Fatalf("expected saved manual test to refresh runtime summary, got %+v", summary.Poll)
}
if summary.Observed == nil || summary.Observed.VMs != 20 || summary.Observed.Networks != 6 {
t.Fatalf("expected saved manual test to refresh observed summary, got %+v", summary.Observed)
}
}
func TestContract_VMwareConnectionListCarriesObservedSummary(t *testing.T) {
setVMwareFeatureForTest(t, true)
connection := config.VMwareVCenterInstance{
ID: "conn-1",
Name: "lab-vcenter",
Host: "vcsa.lab.local",
Port: 443,
Username: "administrator@vsphere.local",
Password: "super-secret",
Enabled: true,
}
handler, persistence := newVMwareHandlersForTest(t)
poller := monitoring.NewVMwarePoller(nil, time.Minute)
handler.getPoller = func(context.Context) *monitoring.VMwarePoller { return poller }
if err := persistence.SaveVMwareConfig([]config.VMwareVCenterInstance{connection}); err != nil {
t.Fatalf("seed vmware config: %v", err)
}
collectedAt := time.Date(2026, 3, 30, 18, 0, 0, 0, time.UTC)
poller.RecordConnectionTestSuccess("default", connection.ID, &vmware.InventorySummary{
Hosts: 4,
VMs: 24,
Datastores: 6,
Networks: 8,
VIRelease: "8.0.3",
}, collectedAt)
req := httptest.NewRequest(http.MethodGet, "/api/vmware/connections", nil)
rec := httptest.NewRecorder()
handler.HandleList(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("expected 200, got %d: %s", rec.Code, rec.Body.String())
}
var responses []vmwareConnectionResponse
if err := json.Unmarshal(rec.Body.Bytes(), &responses); err != nil {
t.Fatalf("unmarshal vmware list response: %v", err)
}
if len(responses) != 1 {
t.Fatalf("expected 1 vmware connection response, got %d", len(responses))
}
if responses[0].Password != "********" {
t.Fatalf("expected redacted password in vmware list response, got %q", responses[0].Password)
}
if responses[0].Poll == nil || responses[0].Poll.LastSuccessAt == nil {
t.Fatalf("expected poll summary in vmware list response, got %+v", responses[0])
}
if responses[0].Observed == nil {
t.Fatalf("expected observed summary in vmware list response, got %+v", responses[0])
}
if got := responses[0].Observed.Hosts; got != 4 {
t.Fatalf("observed hosts = %d, want 4", got)
}
if got := responses[0].Observed.VMs; got != 24 {
t.Fatalf("observed vms = %d, want 24", got)
}
if got := responses[0].Observed.Datastores; got != 6 {
t.Fatalf("observed datastores = %d, want 6", got)
}
if got := responses[0].Observed.Networks; got != 8 {
t.Fatalf("observed networks = %d, want 8", got)
}
if got := responses[0].Observed.VIRelease; got != "8.0.3" {
t.Fatalf("observed viRelease = %q, want 8.0.3", got)
}
if responses[0].Observed.CollectedAt == nil || !responses[0].Observed.CollectedAt.Equal(collectedAt) {
t.Fatalf("observed collectedAt = %+v, want %s", responses[0].Observed.CollectedAt, collectedAt.Format(time.RFC3339))
}
}
type mixedCadenceMetricSample struct {
timestamp time.Time
percentValue float64
rateValue float64
}
func mixedCadenceLongRangeMetricSamples(now time.Time) []mixedCadenceMetricSample {
windowStart := now.Add(-7 * 24 * time.Hour)
samples := make([]mixedCadenceMetricSample, 0, 120)
appendRange := func(start, end time.Time, step time.Duration, percentValue, rateValue float64) {
for ts := start; ts.Before(end); ts = ts.Add(step) {
samples = append(samples, mixedCadenceMetricSample{
timestamp: ts,
percentValue: percentValue,
rateValue: rateValue,
})
}
}
appendRange(windowStart, now.Add(-24*time.Hour), 3*time.Hour, 20, 50)
appendRange(now.Add(-24*time.Hour), now.Add(-2*time.Hour), 30*time.Minute, 40, 75)
appendRange(now.Add(-2*time.Hour), now, 5*time.Minute, 60, 100)
samples = append(samples, mixedCadenceMetricSample{
timestamp: now,
percentValue: 75,
rateValue: 125,
})
return samples
}
func TestContract_InfrastructureChartsNormalizeLongRangeMixedCadence(t *testing.T) {
store := newTestMetricsStore(t)
monitor, state, _ := newTestMonitor(t)
setTestUnexportedField(t, monitor, "metricsStore", store)
state.Nodes = []models.Node{{
ID: "node-contract-1",
Name: "node-contract-1",
Instance: "pve1",
Status: "online",
CPU: 0.75,
Memory: models.Memory{Usage: 42.0},
Disk: models.Disk{Usage: 55.0},
}}
syncTestResourceStore(t, monitor, state)
now := time.Now().UTC().Add(-10 * time.Minute).Truncate(time.Minute)
samples := mixedCadenceLongRangeMetricSamples(now)
seed := make([]metrics.WriteMetric, 0, len(samples)*3)
appendMetric := func(ts time.Time, value float64) {
for _, metricType := range []string{"cpu", "memory", "disk"} {
seed = append(seed, metrics.WriteMetric{
ResourceType: "node",
ResourceID: "node-contract-1",
MetricType: metricType,
Value: value,
Timestamp: ts,
Tier: metrics.TierMinute,
})
}
}
for _, sample := range samples {
appendMetric(sample.timestamp, sample.percentValue)
}
store.WriteBatchSync(seed)
router := &Router{monitor: monitor}
req := httptest.NewRequest(http.MethodGet, "/api/charts/infrastructure?range=7d", nil)
rec := httptest.NewRecorder()
router.handleInfrastructureCharts(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("expected 200, got %d: %s", rec.Code, rec.Body.String())
}
var decoded InfrastructureChartsResponse
if err := json.Unmarshal(rec.Body.Bytes(), &decoded); err != nil {
t.Fatalf("unmarshal infrastructure charts response: %v", err)
}
cpuSeries := decoded.NodeData["node-contract-1"]["cpu"]
if len(cpuSeries) == 0 {
t.Fatal("expected normalized cpu series")
}
if len(cpuSeries) > infrastructureSummaryMaxSeriesPoints {
t.Fatalf("expected cpu series <= %d points, got %d", infrastructureSummaryMaxSeriesPoints, len(cpuSeries))
}
if cpuSeries[len(cpuSeries)-1].Timestamp != now.UnixMilli() {
t.Fatalf("expected latest cpu timestamp %d, got %d", now.UnixMilli(), cpuSeries[len(cpuSeries)-1].Timestamp)
}
if cpuSeries[len(cpuSeries)-1].Value != 75 {
t.Fatalf("expected latest cpu value 75, got %.2f", cpuSeries[len(cpuSeries)-1].Value)
}
recentWindowStart := now.Add(-24 * time.Hour).UnixMilli()
recentCount := 0
for _, point := range cpuSeries {
if point.Timestamp >= recentWindowStart {
recentCount++
}
}
if recentCount > 20 {
t.Fatalf("expected day-proportional recent summary buckets, got %d recent cpu points", recentCount)
}
}
func TestContract_InfrastructureChartsHonorExplicitMetricFilters(t *testing.T) {
monitor, state, _ := newTestMonitor(t)
state.Nodes = []models.Node{{
ID: "node-contract-1",
Name: "node-contract-1",
Status: "online",
CPU: 0.1,
Memory: models.Memory{Usage: 12.0},
Disk: models.Disk{Usage: 34.0},
}}
state.DockerHosts = []models.DockerHost{{
ID: "docker-host-contract-1",
Runtime: "docker",
CPUUsage: 23.0,
Memory: models.Memory{Usage: 45.0},
Disks: []models.Disk{{Usage: 67.0}},
Status: "online",
}}
state.Hosts = []models.Host{{
ID: "agent-contract-1",
Hostname: "agent-contract-1",
CPUUsage: 11.0,
Memory: models.Memory{Usage: 22.0},
Disks: []models.Disk{{Usage: 33.0}},
Status: "online",
}}
syncTestResourceStore(t, monitor, state)
router := &Router{monitor: monitor}
req := httptest.NewRequest(
http.MethodGet,
"/api/charts/infrastructure?range=5m&metrics=cpu,memory",
nil,
)
rec := httptest.NewRecorder()
router.handleInfrastructureCharts(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("expected 200, got %d: %s", rec.Code, rec.Body.String())
}
var decoded InfrastructureChartsResponse
if err := json.Unmarshal(rec.Body.Bytes(), &decoded); err != nil {
t.Fatalf("unmarshal infrastructure charts response: %v", err)
}
if decoded.Stats.PointCounts.Nodes != 2 {
t.Fatalf("expected stats.pointCounts.nodes=2, got %d", decoded.Stats.PointCounts.Nodes)
}
if decoded.Stats.PointCounts.DockerHosts != 2 {
t.Fatalf(
"expected stats.pointCounts.dockerHosts=2, got %d",
decoded.Stats.PointCounts.DockerHosts,
)
}
if decoded.Stats.PointCounts.Agents != 2 {
t.Fatalf("expected stats.pointCounts.agents=2, got %d", decoded.Stats.PointCounts.Agents)
}
if _, ok := decoded.NodeData["node-contract-1"]["disk"]; ok {
t.Fatal("expected node disk series to be filtered out of infrastructure summary payload")
}
if got := len(decoded.NodeData["node-contract-1"]); got != 2 {
t.Fatalf("expected node payload to contain only requested metrics, got %d entries", got)
}
if _, ok := decoded.DockerHostData["docker-host-contract-1"]["disk"]; ok {
t.Fatal("expected docker-host disk series to be filtered out of infrastructure summary payload")
}
if got := len(decoded.DockerHostData["docker-host-contract-1"]); got != 2 {
t.Fatalf("expected docker-host payload to contain only requested metrics, got %d entries", got)
}
if _, ok := decoded.AgentData["agent-contract-1"]["disk"]; ok {
t.Fatal("expected agent disk series to be filtered out of infrastructure summary payload")
}
if got := len(decoded.AgentData["agent-contract-1"]); got != 2 {
t.Fatalf("expected agent payload to contain only requested metrics, got %d entries", got)
}
}
func TestContract_MockChartRoutesUseCanonicalMockUnifiedReadStateForVMwareHosts(t *testing.T) {
setMockModeForTest(t, true)
fixtures := vmware.DefaultFixtures()
if len(fixtures.Hosts) == 0 {
t.Fatal("expected default VMware fixtures to include at least one host")
}
expectedHostID := vmware.SourceID(fixtures.ConnectionID, "host", fixtures.Hosts[0].Host)
monitor, state, _ := newTestMonitor(t)
state.Hosts = []models.Host{{
ID: "live-store-host-1",
Hostname: "live-store-host-1",
CPUUsage: 11.0,
Memory: models.Memory{Usage: 22.0},
Disks: []models.Disk{{Usage: 33.0}},
Status: "online",
}}
syncTestResourceStore(t, monitor, state)
router := &Router{monitor: monitor}
for _, path := range []string{"/api/charts?range=5m", "/api/charts/infrastructure?range=5m"} {
req := httptest.NewRequest(http.MethodGet, path, nil)
rec := httptest.NewRecorder()
switch path {
case "/api/charts?range=5m":
router.handleCharts(rec, req)
case "/api/charts/infrastructure?range=5m":
router.handleInfrastructureCharts(rec, req)
default:
t.Fatalf("unexpected path %q", path)
}
if rec.Code != http.StatusOK {
t.Fatalf("%s status = %d, body=%s", path, rec.Code, rec.Body.String())
}
agentData := map[string]VMChartData{}
if path == "/api/charts?range=5m" {
var decoded ChartResponse
if err := json.Unmarshal(rec.Body.Bytes(), &decoded); err != nil {
t.Fatalf("decode %s: %v", path, err)
}
agentData = decoded.AgentData
} else {
var decoded InfrastructureChartsResponse
if err := json.Unmarshal(rec.Body.Bytes(), &decoded); err != nil {
t.Fatalf("decode %s: %v", path, err)
}
agentData = decoded.AgentData
}
series, ok := agentData[expectedHostID]
if !ok {
t.Fatalf("%s missing VMware host %q; got keys=%v", path, expectedHostID, sortedVMChartKeys(agentData))
}
if len(series["cpu"]) == 0 {
t.Fatalf("%s expected VMware host %q cpu series", path, expectedHostID)
}
if _, ok := agentData["live-store-host-1"]; ok {
t.Fatalf("%s unexpectedly used live store-only host instead of canonical mock snapshot; keys=%v", path, sortedVMChartKeys(agentData))
}
}
}
func TestContract_WorkloadChartsCapLongRangeMixedCadenceByTime(t *testing.T) {
store := newTestMetricsStore(t)
monitor, state, _ := newTestMonitor(t)
setTestUnexportedField(t, monitor, "metricsStore", store)
state.Nodes = []models.Node{{
ID: "node-contract-1",
Name: "node-contract-1",
Instance: "pve1",
Status: "online",
}}
state.VMs = []models.VM{{
ID: "vm-contract-1",
VMID: 101,
Name: "vm-contract-1",
Node: "node-contract-1",
Instance: "pve1",
Status: "running",
CPU: 0.75,
Memory: models.Memory{Usage: 42.0},
Disk: models.Disk{Usage: 55.0},
NetworkIn: 128,
NetworkOut: 256,
DiskRead: 512,
DiskWrite: 256,
}}
syncTestResourceStore(t, monitor, state)
readState := monitor.GetUnifiedReadStateOrSnapshot()
vms := readState.VMs()
if len(vms) != 1 {
t.Fatalf("expected 1 vm view, got %d", len(vms))
}
sourceID := strings.TrimSpace(vms[0].SourceID())
if sourceID == "" {
t.Fatal("expected vm source ID")
}
now := time.Now().UTC().Add(-10 * time.Minute).Truncate(time.Minute)
samples := mixedCadenceLongRangeMetricSamples(now)
seed := make([]metrics.WriteMetric, 0, len(samples)*7)
appendMetric := func(ts time.Time, percentValue, rateValue float64) {
for _, metricType := range []string{"cpu", "memory", "disk"} {
seed = append(seed, metrics.WriteMetric{
ResourceType: "vm",
ResourceID: sourceID,
MetricType: metricType,
Value: percentValue,
Timestamp: ts,
Tier: metrics.TierMinute,
})
}
for _, metricType := range []string{"diskread", "diskwrite", "netin", "netout"} {
seed = append(seed, metrics.WriteMetric{
ResourceType: "vm",
ResourceID: sourceID,
MetricType: metricType,
Value: rateValue,
Timestamp: ts,
Tier: metrics.TierMinute,
})
}
}
for _, sample := range samples {
appendMetric(sample.timestamp, sample.percentValue, sample.rateValue)
}
store.WriteBatchSync(seed)
router := &Router{monitor: monitor}
req := httptest.NewRequest(http.MethodGet, "/api/charts/workloads?range=7d&maxPoints=30", nil)
rec := httptest.NewRecorder()
router.handleWorkloadCharts(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("expected 200, got %d: %s", rec.Code, rec.Body.String())
}
var decoded WorkloadChartsResponse
if err := json.Unmarshal(rec.Body.Bytes(), &decoded); err != nil {
t.Fatalf("unmarshal workload charts response: %v", err)
}
if len(decoded.ChartData) != 1 {
t.Fatalf("expected 1 workload chart entry, got %d", len(decoded.ChartData))
}
var cpuSeries []MetricPoint
for _, chartData := range decoded.ChartData {
cpuSeries = chartData["cpu"]
break
}
if len(cpuSeries) == 0 {
t.Fatal("expected capped cpu series")
}
if len(cpuSeries) > 30 {
t.Fatalf("expected cpu series <= 30 points, got %d", len(cpuSeries))
}
if cpuSeries[len(cpuSeries)-1].Timestamp != now.UnixMilli() {
t.Fatalf("expected latest cpu timestamp %d, got %d", now.UnixMilli(), cpuSeries[len(cpuSeries)-1].Timestamp)
}
recentWindowStart := now.Add(-24 * time.Hour).UnixMilli()
recentCount := 0
for _, point := range cpuSeries {
if point.Timestamp >= recentWindowStart {
recentCount++
}
}
if recentCount > 8 {
t.Fatalf("expected day-proportional capped workload points, got %d recent cpu points", recentCount)
}
}
func TestContract_WorkloadChartsUseCanonicalWorkloadIDsForProviderBackedVMs(t *testing.T) {
monitor, state, history := newTestMonitor(t)
now := time.Now().UTC().Add(-10 * time.Minute).Truncate(time.Minute)
metricID := "vc-1:vm:vm-201"
history.AddGuestMetric(metricID, "cpu", 51, now.Add(-10*time.Minute))
history.AddGuestMetric(metricID, "memory", 64, now.Add(-5*time.Minute))
history.AddGuestMetric(metricID, "disk", 43, now.Add(-3*time.Minute))
history.AddGuestMetric(metricID, "netin", 1200, now.Add(-2*time.Minute))
history.AddGuestMetric(metricID, "netout", 800, now.Add(-2*time.Minute))
adapter := unifiedresources.NewMonitorAdapter(nil)
adapter.PopulateSnapshotAndSupplemental(state.GetSnapshot(), map[unifiedresources.DataSource][]unifiedresources.IngestRecord{
unifiedresources.SourceVMware: {
{
SourceID: metricID,
Resource: unifiedresources.Resource{
ID: "vm-vmware-contract",
Type: unifiedresources.ResourceTypeVM,
Name: "warehouse-api-01",
Status: unifiedresources.StatusOnline,
LastSeen: now,
MetricsTarget: &unifiedresources.MetricsTarget{
ResourceType: "vm",
ResourceID: metricID,
},
VMware: &unifiedresources.VMwareData{
ConnectionID: "vc-1",
EntityType: "vm",
ManagedObjectID: "vm-201",
},
},
},
},
})
setTestUnexportedField(t, monitor, "resourceStore", monitoring.ResourceStoreInterface(adapter))
readState := monitor.GetUnifiedReadStateOrSnapshot()
if readState == nil || len(readState.VMs()) != 1 || readState.VMs()[0] == nil {
t.Fatalf("expected one provider-backed VM in read state, got %+v", readState)
}
resourceID, _, ok := vmChartRequest(readState.VMs()[0])
if !ok {
t.Fatal("expected canonical vm chart request")
}
router := &Router{monitor: monitor}
workloadReq := httptest.NewRequest(http.MethodGet, "/api/charts/workloads?range=1h", nil)
workloadRec := httptest.NewRecorder()
router.handleWorkloadCharts(workloadRec, workloadReq)
if workloadRec.Code != http.StatusOK {
t.Fatalf("expected workload charts 200, got %d: %s", workloadRec.Code, workloadRec.Body.String())
}
var workloadDecoded WorkloadChartsResponse
if err := json.Unmarshal(workloadRec.Body.Bytes(), &workloadDecoded); err != nil {
t.Fatalf("unmarshal workload charts response: %v", err)
}
if _, ok := workloadDecoded.ChartData[resourceID]; !ok {
t.Fatalf("expected workload charts keyed by canonical workload id %q, got %v", resourceID, workloadDecoded.ChartData)
}
if _, ok := workloadDecoded.ChartData[metricID]; ok {
t.Fatalf("expected provider metrics target %q to stay out of workload chart response keys", metricID)
}
if workloadDecoded.GuestTypes[resourceID] != "vm" {
t.Fatalf("expected vm guest type for %q, got %q", resourceID, workloadDecoded.GuestTypes[resourceID])
}
summaryReq := httptest.NewRequest(http.MethodGet, "/api/charts/workloads-summary?range=1h", nil)
summaryRec := httptest.NewRecorder()
router.handleWorkloadsSummaryCharts(summaryRec, summaryReq)
if summaryRec.Code != http.StatusOK {
t.Fatalf("expected workloads summary 200, got %d: %s", summaryRec.Code, summaryRec.Body.String())
}
var summaryDecoded WorkloadsSummaryChartsResponse
if err := json.Unmarshal(summaryRec.Body.Bytes(), &summaryDecoded); err != nil {
t.Fatalf("unmarshal workloads summary response: %v", err)
}
if summaryDecoded.GuestCounts.Total != 1 || summaryDecoded.GuestCounts.Running != 1 {
t.Fatalf("expected stable provider-backed guest counts, got %+v", summaryDecoded.GuestCounts)
}
if len(summaryDecoded.TopContributors.CPU) == 0 {
t.Fatal("expected provider-backed cpu top contributor")
}
if summaryDecoded.TopContributors.CPU[0].ID != resourceID {
t.Fatalf("expected workloads summary contributor id %q, got %+v", resourceID, summaryDecoded.TopContributors.CPU[0])
}
if summaryDecoded.TopContributors.CPU[0].ID == metricID {
t.Fatalf("expected workloads summary contributor id to avoid raw metrics target %q", metricID)
}
}
func TestContract_WorkloadsSummaryChartsNormalizeLongRangeMixedCadence(t *testing.T) {
store := newTestMetricsStore(t)
monitor, state, _ := newTestMonitor(t)
setTestUnexportedField(t, monitor, "metricsStore", store)
state.Nodes = []models.Node{{
ID: "node-contract-1",
Name: "node-contract-1",
Instance: "pve1",
Status: "online",
}}
state.VMs = []models.VM{{
ID: "vm-contract-1",
VMID: 101,
Name: "vm-contract-1",
Node: "node-contract-1",
Instance: "pve1",
Status: "running",
CPU: 0.75,
Memory: models.Memory{Usage: 42.0},
Disk: models.Disk{Usage: 55.0},
NetworkIn: 128,
NetworkOut: 256,
}}
syncTestResourceStore(t, monitor, state)
readState := monitor.GetUnifiedReadStateOrSnapshot()
vms := readState.VMs()
if len(vms) != 1 {
t.Fatalf("expected 1 vm view, got %d", len(vms))
}
sourceID := strings.TrimSpace(vms[0].SourceID())
if sourceID == "" {
t.Fatal("expected vm source ID")
}
now := time.Now().UTC().Add(-10 * time.Minute).Truncate(time.Minute)
samples := mixedCadenceLongRangeMetricSamples(now)
seed := make([]metrics.WriteMetric, 0, len(samples)*5)
appendMetric := func(ts time.Time, percentValue, rateValue float64) {
for _, metricType := range []string{"cpu", "memory", "disk"} {
seed = append(seed, metrics.WriteMetric{
ResourceType: "vm",
ResourceID: sourceID,
MetricType: metricType,
Value: percentValue,
Timestamp: ts,
Tier: metrics.TierMinute,
})
}
for _, metricType := range []string{"netin", "netout"} {
seed = append(seed, metrics.WriteMetric{
ResourceType: "vm",
ResourceID: sourceID,
MetricType: metricType,
Value: rateValue,
Timestamp: ts,
Tier: metrics.TierMinute,
})
}
}
for _, sample := range samples {
appendMetric(sample.timestamp, sample.percentValue, sample.rateValue)
}
store.WriteBatchSync(seed)
router := &Router{monitor: monitor}
req := httptest.NewRequest(http.MethodGet, "/api/charts/workloads-summary?range=7d", nil)
rec := httptest.NewRecorder()
router.handleWorkloadsSummaryCharts(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("expected 200, got %d: %s", rec.Code, rec.Body.String())
}
var decoded WorkloadsSummaryChartsResponse
if err := json.Unmarshal(rec.Body.Bytes(), &decoded); err != nil {
t.Fatalf("unmarshal workloads summary charts response: %v", err)
}
if len(decoded.CPU.P50) == 0 {
t.Fatal("expected normalized workload summary p50 series")
}
if len(decoded.CPU.P50) > workloadsSummaryMaxSeriesPoints {
t.Fatalf("expected workload summary p50 <= %d points, got %d", workloadsSummaryMaxSeriesPoints, len(decoded.CPU.P50))
}
if len(decoded.CPU.P95) > workloadsSummaryMaxSeriesPoints {
t.Fatalf("expected workload summary p95 <= %d points, got %d", workloadsSummaryMaxSeriesPoints, len(decoded.CPU.P95))
}
if decoded.CPU.P50[len(decoded.CPU.P50)-1].Timestamp != now.UnixMilli() {
t.Fatalf("expected latest workload summary timestamp %d, got %d", now.UnixMilli(), decoded.CPU.P50[len(decoded.CPU.P50)-1].Timestamp)
}
recentWindowStart := now.Add(-24 * time.Hour).UnixMilli()
recentCount := 0
for _, point := range decoded.CPU.P50 {
if point.Timestamp >= recentWindowStart {
recentCount++
}
}
if recentCount > 20 {
t.Fatalf("expected day-proportional summary buckets, got %d recent p50 points", recentCount)
}
}
func TestContract_WorkloadChartMetricBudgetGuardrailsRemainCanonical(t *testing.T) {
data, err := os.ReadFile("router.go")
if err != nil {
t.Fatalf("failed to read router.go: %v", err)
}
source := string(data)
requiredSnippets := []string{
`var workloadSummaryMetricOrder = []string{`,
`"cpu",`,
`"memory",`,
`"disk",`,
`"netin",`,
`"netout",`,
`var workloadChartsBatchWG sync.WaitGroup`,
`workloadChartsBatchWG.Add(4)`,
`podBatchMetrics = monitor.GetGuestMetricsForChartBatch("k8s", podRequests, duration, workloadSummaryMetricOrder...)`,
`var workloadsSummaryBatchWG sync.WaitGroup`,
`workloadsSummaryBatchWG.Add(4)`,
`vmBatchMetrics = monitor.GetGuestMetricsForChartBatch("vm", vmRequests, duration, workloadSummaryMetricOrder...)`,
`containerBatchMetrics = monitor.GetGuestMetricsForChartBatch("container", containerRequests, duration, workloadSummaryMetricOrder...)`,
`dockerContainerBatchMetrics = monitor.GetGuestMetricsForChartBatch("dockerContainer", dockerContainerRequests, duration, workloadSummaryMetricOrder...)`,
`summaryChartsCacheTTL = 5 * time.Second`,
`infrastructureChartsCacheKey(req *http.Request, timeRange string, requestedMetricNames []string) string`,
`cachedInfrastructureChartsPayload`,
`cacheInfrastructureChartsPayload`,
`workloadsSummaryChartsCacheKey`,
`cachedWorkloadsSummaryChartsPayload`,
`cacheWorkloadsSummaryChartsPayload`,
`type workloadSummaryMetricBucket struct`,
}
for _, snippet := range requiredSnippets {
if !strings.Contains(source, snippet) {
t.Fatalf("router.go must contain %q", snippet)
}
}
}
func TestContract_GenerateStyledMockSeries_UsesTimestampBasedCurve(t *testing.T) {
now := time.Date(2026, time.March, 31, 12, 0, 0, 0, time.UTC).UnixMilli()
coarse := generateStyledMockSeries(
now,
time.Hour,
7,
51.9,
"dockerContainer",
"orion-2-f54579833f9c",
"memory",
)
fine := generateStyledMockSeries(
now,
time.Hour,
13,
51.9,
"dockerContainer",
"orion-2-f54579833f9c",
"memory",
)
if len(coarse) != 7 || len(fine) != 13 {
t.Fatalf("unexpected synthetic series lengths coarse=%d fine=%d", len(coarse), len(fine))
}
for i, point := range coarse {
fineIndex := i * 2
if fine[fineIndex].Timestamp != point.Timestamp {
t.Fatalf(
"expected shared timestamp at coarse[%d]=%d to match fine[%d]=%d",
i,
point.Timestamp,
fineIndex,
fine[fineIndex].Timestamp,
)
}
if fine[fineIndex].Value != point.Value {
t.Fatalf(
"expected shared timestamp value at coarse[%d]=%f to match fine[%d]=%f",
i,
point.Value,
fineIndex,
fine[fineIndex].Value,
)
}
}
}
func TestContract_PlatformMockToggleRebindsRuntimeConnectionsAndResources(t *testing.T) {
setMockModeForTest(t, false)
cfg := &config.Config{DataPath: t.TempDir()}
monitor, err := monitoring.New(cfg)
if err != nil {
t.Fatalf("new monitor: %v", err)
}
if err := monitor.SetMockMode(false); err != nil {
t.Fatalf("set monitor mock mode: %v", err)
}
router := NewRouter(cfg, monitor, nil, nil, nil, "1.0.0")
t.Cleanup(func() {
router.shutdownBackgroundWorkers()
})
toggleReq := httptest.NewRequest(http.MethodPost, "/api/system/mock-mode", strings.NewReader(`{"enabled":true}`))
toggleRec := httptest.NewRecorder()
router.configHandlers.HandleUpdateMockMode(toggleRec, toggleReq)
if toggleRec.Code != http.StatusOK {
t.Fatalf("toggle status = %d, body=%s", toggleRec.Code, toggleRec.Body.String())
}
truenasListRec := httptest.NewRecorder()
truenasListReq := httptest.NewRequest(http.MethodGet, "/api/truenas/connections", nil)
router.trueNASHandlers.HandleList(truenasListRec, truenasListReq)
if truenasListRec.Code != http.StatusOK {
t.Fatalf("truenas connections status = %d, body=%s", truenasListRec.Code, truenasListRec.Body.String())
}
var truenasConnections []trueNASConnectionResponse
if err := json.Unmarshal(truenasListRec.Body.Bytes(), &truenasConnections); err != nil {
t.Fatalf("decode truenas connections: %v", err)
}
if len(truenasConnections) != 1 || truenasConnections[0].ID != "truenas-mock-1" {
t.Fatalf("expected mock truenas connection, got %#v", truenasConnections)
}
vmwareListRec := httptest.NewRecorder()
vmwareListReq := httptest.NewRequest(http.MethodGet, "/api/vmware/connections", nil)
router.vmwareHandlers.HandleList(vmwareListRec, vmwareListReq)
if vmwareListRec.Code != http.StatusOK {
t.Fatalf("vmware connections status = %d, body=%s", vmwareListRec.Code, vmwareListRec.Body.String())
}
var vmwareConnections []vmwareConnectionResponse
if err := json.Unmarshal(vmwareListRec.Body.Bytes(), &vmwareConnections); err != nil {
t.Fatalf("decode vmware connections: %v", err)
}
if len(vmwareConnections) != 1 || vmwareConnections[0].ID != "vc-mock-1" {
t.Fatalf("expected mock vmware connection, got %#v", vmwareConnections)
}
assertResourceSource := func(path string, wantSource unifiedresources.DataSource) {
t.Helper()
rec := httptest.NewRecorder()
req := httptest.NewRequest(http.MethodGet, path, nil)
router.resourceHandlers.HandleListResources(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("%s status = %d, body=%s", path, rec.Code, rec.Body.String())
}
var resp ResourcesResponse
if err := json.Unmarshal(rec.Body.Bytes(), &resp); err != nil {
t.Fatalf("decode %s response: %v", path, err)
}
for _, resource := range resp.Data {
for _, source := range resource.Sources {
if source == wantSource {
return
}
}
}
t.Fatalf("expected %s response to include source %q, got %#v", path, wantSource, resp.Data)
}
assertResourceSource("/api/resources?source=truenas", unifiedresources.SourceTrueNAS)
assertResourceSource("/api/resources?source=vmware-vsphere", unifiedresources.SourceVMware)
assertResourceCount := func(path string, want int) {
t.Helper()
rec := httptest.NewRecorder()
req := httptest.NewRequest(http.MethodGet, path, nil)
router.resourceHandlers.HandleListResources(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("%s status = %d, body=%s", path, rec.Code, rec.Body.String())
}
var resp ResourcesResponse
if err := json.Unmarshal(rec.Body.Bytes(), &resp); err != nil {
t.Fatalf("decode %s response: %v", path, err)
}
if len(resp.Data) != want {
t.Fatalf("%s returned %d resources, want %d", path, len(resp.Data), want)
}
}
assertResourceCount("/api/resources?source=truenas&type=app-container", len(truenas.DefaultFixtures().Apps))
assertResourceCount("/api/resources?source=truenas&type=network-share", len(truenas.DefaultFixtures().Shares))
assertResourceCount("/api/resources?source=vmware-vsphere&type=storage", len(vmware.DefaultFixtures().Datastores))
assertResourceCount("/api/resources?source=vmware-vsphere&type=network", len(vmware.DefaultFixtures().Networks))
}
func TestContract_PlatformMockConnectionListsUseSharedFixtureMetadata(t *testing.T) {
setTrueNASFeatureForTest(t, true)
setVMwareFeatureForTest(t, true)
setMockModeForTest(t, true)
t.Run("truenas", func(t *testing.T) {
fixture := mock.DefaultTrueNASConnectionFixture()
if fixture.CollectedAt.IsZero() {
t.Fatal("expected canonical truenas mock fixture timestamp")
}
handler, _, _ := newTrueNASHandlersForTest(t, nil)
req := httptest.NewRequest(http.MethodGet, "/api/truenas/connections", nil)
rec := httptest.NewRecorder()
handler.HandleList(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("expected 200, got %d: %s", rec.Code, rec.Body.String())
}
var responses []trueNASConnectionResponse
if err := json.Unmarshal(rec.Body.Bytes(), &responses); err != nil {
t.Fatalf("decode truenas mock list response: %v", err)
}
if len(responses) != 1 {
t.Fatalf("expected 1 mock truenas connection, got %d", len(responses))
}
response := responses[0]
if response.ID != fixture.ID || response.Name != fixture.Name || response.Host != fixture.Host || response.Port != fixture.Port {
t.Fatalf("unexpected truenas mock connection metadata: got %+v want fixture %+v", response.TrueNASInstance, fixture)
}
if response.APIKey != "********" {
t.Fatalf("expected redacted truenas api key, got %q", response.APIKey)
}
if response.Poll == nil || response.Poll.IntervalSeconds != fixture.PollIntervalSeconds {
t.Fatalf("expected truenas poll interval %d, got %+v", fixture.PollIntervalSeconds, response.Poll)
}
if response.Poll.LastSuccessAt == nil || !response.Poll.LastSuccessAt.Equal(fixture.CollectedAt) {
t.Fatalf("expected truenas last success at %s, got %+v", fixture.CollectedAt.Format(time.RFC3339), response.Poll)
}
if response.Observed == nil {
t.Fatal("expected truenas observed summary")
}
if response.Observed.Host != fixture.Host ||
response.Observed.ResourceID != fixture.ResourceID ||
response.Observed.Systems != fixture.Systems ||
response.Observed.StoragePools != fixture.StoragePools ||
response.Observed.Datasets != fixture.Datasets ||
response.Observed.Apps != fixture.Apps ||
response.Observed.Disks != fixture.Disks ||
response.Observed.RecoveryArtifacts != fixture.RecoveryArtifacts {
t.Fatalf("unexpected truenas observed summary: got %+v want fixture %+v", response.Observed, fixture)
}
if response.Observed.CollectedAt == nil || !response.Observed.CollectedAt.Equal(fixture.CollectedAt) {
t.Fatalf("expected truenas observed collectedAt %s, got %+v", fixture.CollectedAt.Format(time.RFC3339), response.Observed)
}
})
t.Run("vmware", func(t *testing.T) {
fixture := mock.DefaultVMwareConnectionFixture()
if fixture.CollectedAt.IsZero() {
t.Fatal("expected canonical vmware mock fixture timestamp")
}
handler, _ := newVMwareHandlersForTest(t)
req := httptest.NewRequest(http.MethodGet, "/api/vmware/connections", nil)
rec := httptest.NewRecorder()
handler.HandleList(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("expected 200, got %d: %s", rec.Code, rec.Body.String())
}
var responses []vmwareConnectionResponse
if err := json.Unmarshal(rec.Body.Bytes(), &responses); err != nil {
t.Fatalf("decode vmware mock list response: %v", err)
}
if len(responses) != 1 {
t.Fatalf("expected 1 mock vmware connection, got %d", len(responses))
}
response := responses[0]
if response.ID != fixture.ID || response.Name != fixture.Name || response.Host != fixture.Host || response.Port != fixture.Port || response.Username != fixture.Username {
t.Fatalf("unexpected vmware mock connection metadata: got %+v want fixture %+v", response.VMwareVCenterInstance, fixture)
}
if response.Password != "********" {
t.Fatalf("expected redacted vmware password, got %q", response.Password)
}
if response.Poll == nil || response.Poll.IntervalSeconds != fixture.PollIntervalSeconds {
t.Fatalf("expected vmware poll interval %d, got %+v", fixture.PollIntervalSeconds, response.Poll)
}
if response.Poll.LastSuccessAt == nil || !response.Poll.LastSuccessAt.Equal(fixture.CollectedAt) {
t.Fatalf("expected vmware last success at %s, got %+v", fixture.CollectedAt.Format(time.RFC3339), response.Poll)
}
if response.Observed == nil {
t.Fatal("expected vmware observed summary")
}
if response.Observed.Hosts != fixture.Hosts ||
response.Observed.VMs != fixture.VMs ||
response.Observed.Datastores != fixture.Datastores ||
response.Observed.VIRelease != fixture.VIRelease {
t.Fatalf("unexpected vmware observed summary: got %+v want fixture %+v", response.Observed, fixture)
}
if response.Observed.CollectedAt == nil || !response.Observed.CollectedAt.Equal(fixture.CollectedAt) {
t.Fatalf("expected vmware observed collectedAt %s, got %+v", fixture.CollectedAt.Format(time.RFC3339), response.Observed)
}
})
}
func TestContract_VMwareConnectionListCarriesDegradedObservedSummary(t *testing.T) {
setVMwareFeatureForTest(t, true)
connection := config.VMwareVCenterInstance{
ID: "conn-1",
Name: "lab-vcenter",
Host: "vcsa.lab.local",
Port: 443,
Username: "administrator@vsphere.local",
Password: "super-secret",
Enabled: true,
}
handler, persistence := newVMwareHandlersForTest(t)
if err := persistence.SaveVMwareConfig([]config.VMwareVCenterInstance{connection}); err != nil {
t.Fatalf("seed vmware config: %v", err)
}
collectedAt := time.Date(2026, 3, 31, 18, 15, 0, 0, time.UTC)
handler.statusMu.Lock()
handler.statuses = map[string]vmwareConnectionRuntimeStatus{
connection.ID: {
Poll: &monitoring.VMwareConnectionPollStatus{
IntervalSeconds: 60,
LastSuccessAt: &collectedAt,
},
Observed: &monitoring.VMwareConnectionObservedSummary{
CollectedAt: &collectedAt,
Hosts: 4,
VMs: 24,
Datastores: 6,
VIRelease: "8.0.3",
Degraded: true,
IssueCount: 3,
Issues: []monitoring.VMwareConnectionObservedIssue{
{
Stage: "signals",
Category: "permission",
Message: "VMware permissions are insufficient for HostSystem overall status",
Occurrences: 2,
},
{
Stage: "topology",
Category: "unavailable",
Message: "VMware vm guest identity is temporarily unavailable",
Occurrences: 1,
},
},
},
},
}
handler.statusMu.Unlock()
req := httptest.NewRequest(http.MethodGet, "/api/vmware/connections", nil)
rec := httptest.NewRecorder()
handler.HandleList(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("expected 200, got %d: %s", rec.Code, rec.Body.String())
}
var responses []vmwareConnectionResponse
if err := json.Unmarshal(rec.Body.Bytes(), &responses); err != nil {
t.Fatalf("unmarshal vmware degraded list response: %v", err)
}
if len(responses) != 1 || responses[0].Observed == nil {
t.Fatalf("expected 1 vmware connection with degraded observed summary, got %+v", responses)
}
if !responses[0].Observed.Degraded {
t.Fatalf("expected degraded observed summary, got %+v", responses[0].Observed)
}
if responses[0].Observed.IssueCount != 3 {
t.Fatalf("observed issueCount = %d, want 3", responses[0].Observed.IssueCount)
}
if len(responses[0].Observed.Issues) != 2 {
t.Fatalf("observed issues len = %d, want 2", len(responses[0].Observed.Issues))
}
if responses[0].Observed.Issues[0].Stage != "signals" || responses[0].Observed.Issues[0].Occurrences != 2 {
t.Fatalf("unexpected first observed issue: %+v", responses[0].Observed.Issues[0])
}
if responses[0].Observed.Issues[1].Stage != "topology" || responses[0].Observed.Issues[1].Occurrences != 1 {
t.Fatalf("unexpected second observed issue: %+v", responses[0].Observed.Issues[1])
}
}
func TestContract_SSOTestRejectsMetadataURLWithUserinfo(t *testing.T) {
called := make(chan struct{}, 1)
metadataServer := newIPv4HTTPServer(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
select {
case called <- struct{}{}:
default:
}
w.Header().Set("Content-Type", "application/xml")
w.Write([]byte(testSAMLMetadata))
}))
defer metadataServer.Close()
metadataURL, err := url.Parse(metadataServer.URL)
if err != nil {
t.Fatalf("parse metadata server url: %v", err)
}
metadataURL.User = url.UserPassword("user", "pass")
reqBody := SSOTestRequest{
Type: "saml",
SAML: &SAMLTestConfig{
IDPMetadataURL: metadataURL.String(),
},
}
body, err := json.Marshal(reqBody)
if err != nil {
t.Fatalf("marshal request: %v", err)
}
req := httptest.NewRequest(http.MethodPost, "/api/security/sso/providers/test", bytes.NewReader(body))
req.Header.Set("Content-Type", "application/json")
setTestIP(req)
rec := httptest.NewRecorder()
router := &Router{}
router.handleTestSSOProvider(rec, req)
if rec.Code != http.StatusBadRequest {
t.Fatalf("status=%d, want %d: %s", rec.Code, http.StatusBadRequest, rec.Body.String())
}
var resp SSOTestResponse
if err := json.Unmarshal(rec.Body.Bytes(), &resp); err != nil {
t.Fatalf("unmarshal response: %v", err)
}
if resp.Success {
t.Fatalf("expected failed response, got success: %+v", resp)
}
select {
case <-called:
t.Fatal("expected SAML metadata URL with userinfo to be rejected before outbound fetch")
default:
}
}
func TestContract_SSOTestRejectsManualSLOURLWithUserinfo(t *testing.T) {
reqBody := SSOTestRequest{
Type: "saml",
SAML: &SAMLTestConfig{
IDPSSOURL: "https://idp.example.com/sso",
IDPSLOURL: "https://user:pass@idp.example.com/slo",
},
}
body, err := json.Marshal(reqBody)
if err != nil {
t.Fatalf("marshal request: %v", err)
}
req := httptest.NewRequest(http.MethodPost, "/api/security/sso/providers/test", bytes.NewReader(body))
req.Header.Set("Content-Type", "application/json")
setTestIP(req)
rec := httptest.NewRecorder()
router := &Router{}
router.handleTestSSOProvider(rec, req)
if rec.Code != http.StatusBadRequest {
t.Fatalf("status=%d, want %d: %s", rec.Code, http.StatusBadRequest, rec.Body.String())
}
var resp SSOTestResponse
if err := json.Unmarshal(rec.Body.Bytes(), &resp); err != nil {
t.Fatalf("unmarshal response: %v", err)
}
if resp.Success {
t.Fatalf("expected failed response, got success: %+v", resp)
}
if resp.Message != "Invalid SLO URL format" {
t.Fatalf("expected invalid SLO URL message, got %+v", resp)
}
}
func TestContract_SSOTestOIDCDiscoveryKeepsIssuerBasePath(t *testing.T) {
var issuerURL string
discoveryServer := newIPv4HTTPServer(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/auth/realms/pulse/.well-known/openid-configuration" {
http.NotFound(w, r)
return
}
w.Header().Set("Content-Type", "application/json")
fmt.Fprintf(w, `{
"issuer": %q,
"authorization_endpoint": %q,
"token_endpoint": %q,
"userinfo_endpoint": %q,
"jwks_uri": %q,
"scopes_supported": ["openid", "profile", "email"]
}`, issuerURL, issuerURL+"/protocol/openid-connect/auth", issuerURL+"/protocol/openid-connect/token", issuerURL+"/protocol/openid-connect/userinfo", issuerURL+"/protocol/openid-connect/certs")
}))
defer discoveryServer.Close()
issuerURL = discoveryServer.URL + "/auth/realms/pulse"
reqBody := SSOTestRequest{
Type: "oidc",
OIDC: &OIDCTestConfig{
IssuerURL: issuerURL,
},
}
body, err := json.Marshal(reqBody)
if err != nil {
t.Fatalf("marshal request: %v", err)
}
req := httptest.NewRequest(http.MethodPost, "/api/security/sso/providers/test", bytes.NewReader(body))
req.Header.Set("Content-Type", "application/json")
setTestIP(req)
rec := httptest.NewRecorder()
router := &Router{}
router.handleTestSSOProvider(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("status=%d, want %d: %s", rec.Code, http.StatusOK, rec.Body.String())
}
var resp SSOTestResponse
if err := json.Unmarshal(rec.Body.Bytes(), &resp); err != nil {
t.Fatalf("unmarshal response: %v", err)
}
if !resp.Success {
t.Fatalf("expected success response, got %+v", resp)
}
if resp.Details == nil {
t.Fatal("expected OIDC details in response")
}
if resp.Details.EntityID != issuerURL {
t.Fatalf("issuer=%q, want %q", resp.Details.EntityID, issuerURL)
}
}
func TestContract_SSOTestRejectsCrossOriginSAMLMetadataRedirect(t *testing.T) {
targetCalled := make(chan struct{}, 1)
target := newIPv4HTTPServer(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
select {
case targetCalled <- struct{}{}:
default:
}
w.Header().Set("Content-Type", "application/xml")
w.Write([]byte(testSAMLMetadata))
}))
defer target.Close()
origin := newIPv4HTTPServer(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, target.URL+r.URL.Path, http.StatusFound)
}))
defer origin.Close()
reqBody := SSOTestRequest{
Type: "saml",
SAML: &SAMLTestConfig{
IDPMetadataURL: origin.URL + "/metadata",
},
}
body, err := json.Marshal(reqBody)
if err != nil {
t.Fatalf("marshal request: %v", err)
}
req := httptest.NewRequest(http.MethodPost, "/api/security/sso/providers/test", bytes.NewReader(body))
req.Header.Set("Content-Type", "application/json")
setTestIP(req)
rec := httptest.NewRecorder()
router := &Router{}
router.handleTestSSOProvider(rec, req)
if rec.Code != http.StatusBadRequest {
t.Fatalf("status=%d, want %d: %s", rec.Code, http.StatusBadRequest, rec.Body.String())
}
select {
case <-targetCalled:
t.Fatal("expected cross-origin SAML redirect to be rejected before fetching the target origin")
default:
}
}
func TestContract_SSOTestRejectsCrossOriginOIDCDiscoveryRedirect(t *testing.T) {
targetCalled := make(chan struct{}, 1)
var targetURL string
target := newIPv4HTTPServer(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/.well-known/openid-configuration" {
http.NotFound(w, r)
return
}
select {
case targetCalled <- struct{}{}:
default:
}
w.Header().Set("Content-Type", "application/json")
fmt.Fprintf(w, `{
"issuer": %q,
"authorization_endpoint": %q,
"token_endpoint": %q,
"userinfo_endpoint": %q,
"jwks_uri": %q,
"scopes_supported": ["openid", "profile", "email"]
}`, targetURL, targetURL+"/auth", targetURL+"/token", targetURL+"/userinfo", targetURL+"/jwks")
}))
defer target.Close()
targetURL = target.URL
origin := newIPv4HTTPServer(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/.well-known/openid-configuration" {
http.NotFound(w, r)
return
}
http.Redirect(w, r, target.URL+r.URL.Path, http.StatusFound)
}))
defer origin.Close()
reqBody := SSOTestRequest{
Type: "oidc",
OIDC: &OIDCTestConfig{
IssuerURL: origin.URL,
},
}
body, err := json.Marshal(reqBody)
if err != nil {
t.Fatalf("marshal request: %v", err)
}
req := httptest.NewRequest(http.MethodPost, "/api/security/sso/providers/test", bytes.NewReader(body))
req.Header.Set("Content-Type", "application/json")
setTestIP(req)
rec := httptest.NewRecorder()
router := &Router{}
router.handleTestSSOProvider(rec, req)
if rec.Code != http.StatusBadRequest {
t.Fatalf("status=%d, want %d: %s", rec.Code, http.StatusBadRequest, rec.Body.String())
}
select {
case <-targetCalled:
t.Fatal("expected cross-origin OIDC redirect to be rejected before fetching the target origin")
default:
}
}
func TestContract_SSOLocalRedirectTargetsStayCanonical(t *testing.T) {
if got := buildLocalRedirectTarget("https://evil.example.com/pwn", map[string]string{
"oidc": "error",
}); got != "/?oidc=error" {
t.Fatalf("absolute redirect target = %q, want %q", got, "/?oidc=error")
}
if got := buildLocalRedirectTarget("/login?foo=bar#section", map[string]string{
"saml": "success",
}); got != "/login?foo=bar&saml=success#section" {
t.Fatalf("local redirect target = %q, want %q", got, "/login?foo=bar&saml=success#section")
}
}
func TestContract_SAMLLoginRejectsUnsupportedMethods(t *testing.T) {
router := newSAMLRouter(t, testSAMLProvider("okta", true))
req := httptest.NewRequest(http.MethodPut, "/api/saml/okta/login", nil)
rec := httptest.NewRecorder()
router.handleSAMLLogin(rec, req)
if rec.Code != http.StatusMethodNotAllowed {
t.Fatalf("status=%d, want %d: %s", rec.Code, http.StatusMethodNotAllowed, rec.Body.String())
}
}
func TestContract_FindingJSONSnapshot(t *testing.T) {
now := time.Date(2026, 2, 8, 13, 14, 15, 0, time.UTC)
lastSeen := now.Add(5 * time.Minute)
resolvedAt := now.Add(10 * time.Minute)
ackAt := now.Add(11 * time.Minute)
snoozedUntil := now.Add(12 * time.Minute)
lastInvestigated := now.Add(15 * time.Minute)
lastRegression := now.Add(30 * time.Minute)
payload := ai.Finding{
ID: "finding-1",
Key: "cpu-high",
Severity: ai.FindingSeverityCritical,
Category: ai.FindingCategoryPerformance,
ResourceID: "vm-100",
ResourceName: "web-server",
ResourceType: "vm",
Node: "pve-1",
Title: "High CPU usage",
Description: "CPU sustained above 95%",
Recommendation: "Investigate processes and load",
Evidence: "cpu=96%",
Source: "ai-analysis",
DetectedAt: now,
LastSeenAt: lastSeen,
ResolvedAt: &resolvedAt,
AutoResolved: true,
ResolveReason: "No longer detected",
AcknowledgedAt: &ackAt,
SnoozedUntil: &snoozedUntil,
AlertIdentifier: "alert-1",
DismissedReason: "expected_behavior",
UserNote: "Runs nightly batch",
TimesRaised: 4,
Suppressed: true,
InvestigationSessionID: "inv-session-1",
InvestigationStatus: "completed",
InvestigationOutcome: "fix_queued",
LastInvestigatedAt: &lastInvestigated,
InvestigationAttempts: 2,
InvestigationRecord: &aicontracts.InvestigationRecord{
ID: "investigation-1",
FindingID: "finding-1",
Subject: aicontracts.InvestigationRecordSubject{
ResourceID: "vm-100",
ResourceName: "web-server",
ResourceType: "vm",
Node: "pve-1",
},
Trigger: aicontracts.InvestigationRecordTrigger{
FindingKey: "cpu-high",
Source: "ai-analysis",
Severity: "critical",
Category: "performance",
Title: "High CPU usage",
DetectedAt: now,
Description: "CPU sustained above 95%",
},
Status: aicontracts.InvestigationStatusCompleted,
Outcome: aicontracts.OutcomeFixQueued,
Confidence: aicontracts.InvestigationRecordConfidenceMedium,
Evidence: []aicontracts.InvestigationRecordEvidence{{Kind: "finding_evidence", Summary: "cpu=96%"}},
Conclusion: "CPU sustained above 95%",
RecommendedAction: "Investigate processes and load",
Verification: []string{},
Rollback: []string{},
ToolsUsed: []string{"ssh.exec"},
StartedAt: now,
ApprovalID: "approval-1",
},
LoopState: "remediation_planned",
Lifecycle: []ai.FindingLifecycleEvent{
{
At: now,
Type: "state_change",
Message: "Moved to investigating",
From: "detected",
To: "investigating",
Metadata: map[string]string{
"from": "detected",
"to": "investigating",
},
},
},
RegressionCount: 1,
LastRegressionAt: &lastRegression,
}
got, err := json.Marshal(payload)
if err != nil {
t.Fatalf("marshal finding: %v", err)
}
const want = `{
"id":"finding-1",
"key":"cpu-high",
"severity":"critical",
"category":"performance",
"resource_id":"vm-100",
"resource_name":"web-server",
"resource_type":"vm",
"node":"pve-1",
"title":"High CPU usage",
"description":"CPU sustained above 95%",
"recommendation":"Investigate processes and load",
"evidence":"cpu=96%",
"source":"ai-analysis",
"detected_at":"2026-02-08T13:14:15Z",
"last_seen_at":"2026-02-08T13:19:15Z",
"resolved_at":"2026-02-08T13:24:15Z",
"auto_resolved":true,
"resolve_reason":"No longer detected",
"acknowledged_at":"2026-02-08T13:25:15Z",
"snoozed_until":"2026-02-08T13:26:15Z",
"alert_identifier":"alert-1",
"dismissed_reason":"expected_behavior",
"user_note":"Runs nightly batch",
"times_raised":4,
"suppressed":true,
"investigation_session_id":"inv-session-1",
"investigation_status":"completed",
"investigation_outcome":"fix_queued",
"last_investigated_at":"2026-02-08T13:29:15Z",
"investigation_attempts":2,
"investigation_record":{"id":"investigation-1","finding_id":"finding-1","subject":{"resource_id":"vm-100","resource_name":"web-server","resource_type":"vm","node":"pve-1"},"trigger":{"finding_key":"cpu-high","source":"ai-analysis","severity":"critical","category":"performance","title":"High CPU usage","detected_at":"2026-02-08T13:14:15Z","description":"CPU sustained above 95%"},"status":"completed","outcome":"fix_queued","confidence":"medium","evidence":[{"kind":"finding_evidence","summary":"cpu=96%"}],"conclusion":"CPU sustained above 95%","recommended_action":"Investigate processes and load","verification":[],"rollback":[],"tools_used":["ssh.exec"],"started_at":"2026-02-08T13:14:15Z","approval_id":"approval-1"},
"loop_state":"remediation_planned",
"lifecycle":[{"at":"2026-02-08T13:14:15Z","type":"state_change","message":"Moved to investigating","from":"detected","to":"investigating","metadata":{"from":"detected","to":"investigating"}}],
"regression_count":1,
"last_regression_at":"2026-02-08T13:44:15Z"
}`
assertJSONSnapshot(t, got, want)
}
// TestContract_PatrolStatusTrustJSONSnapshot pins the canonical JSON shape
// for the trust snapshot block carried on the patrol-status response. The
// PatrolStatusResponse.Trust field surfaces FindingsTrustSummary on the
// operator-facing Patrol page so trust signals (fix verified, dismissed as
// noise, etc.) are scannable without expanding individual findings.
func TestContract_PatrolStatusTrustJSONSnapshot(t *testing.T) {
trust := ai.FindingsTrustSummary{
Tracked: 4,
CurrentlyActive: 1,
Resolved: 2,
AutoResolved: 1,
FixVerified: 1,
FixFailed: 0,
DismissedAsNoise: 1,
DismissedAsExpected: 0,
DismissedAsLater: 0,
Suppressed: 0,
RegressedAtLeastOnce: 1,
}
got, err := json.Marshal(trust)
if err != nil {
t.Fatalf("marshal trust summary: %v", err)
}
want := `{"tracked":4,"currently_active":1,"resolved":2,"auto_resolved":1,"fix_verified":1,"fix_failed":0,"dismissed_as_noise":1,"dismissed_as_expected":0,"dismissed_as_later":0,"suppressed":0,"regressed_at_least_once":1}`
if string(got) != want {
t.Fatalf("trust summary JSON drift:\n got: %s\nwant: %s", got, want)
}
}
// TestContract_UnifiedFindingPreviousResolvedFixSummaryJSONSnapshot pins the
// canonical JSON shape for the operational-memory field that preserves the
// prior successful fix summary across regressions. The Finding to
// UnifiedFinding conversion in the API router must emit this field under
// the canonical "previous_resolved_fix_summary" key so Assistant context
// and operator surfaces can reason about "what worked last time."
func TestContract_UnifiedFindingPreviousResolvedFixSummaryJSONSnapshot(t *testing.T) {
finding := unified.UnifiedFinding{
ID: "uf-regress",
Source: unified.SourceAIPatrol,
Severity: unified.SeverityWarning,
Category: unified.CategoryReliability,
ResourceID: "vm-100",
Title: "Service stalled",
Description: "Service stopped responding again",
PreviousResolvedFixSummary: "Restart the workload service after backup window clears",
DetectedAt: time.Date(2026, 5, 8, 12, 0, 0, 0, time.UTC),
LastSeenAt: time.Date(2026, 5, 8, 12, 5, 0, 0, time.UTC),
}
got, err := json.Marshal(finding)
if err != nil {
t.Fatalf("marshal unified finding: %v", err)
}
if !strings.Contains(string(got), `"previous_resolved_fix_summary":"Restart the workload service after backup window clears"`) {
t.Fatalf("expected previous_resolved_fix_summary field in canonical UnifiedFinding payload, got %s", got)
}
}
// TestContract_UnifiedFindingImpactJSONSnapshot pins the canonical JSON shape
// for UnifiedFinding payloads carrying the operator-facing Impact field. The
// AI engine populates Finding.Impact at detection time and the Finding to
// UnifiedFinding conversion in the API router must preserve that string under
// the canonical "impact" key so the operator-visible card shows
// consequence-if-ignored copy.
func TestContract_UnifiedFindingImpactJSONSnapshot(t *testing.T) {
now := time.Date(2026, 2, 8, 13, 14, 15, 0, time.UTC)
finding := unified.UnifiedFinding{
ID: "uf-impact",
Source: unified.SourceAIPatrol,
Severity: unified.SeverityWarning,
Category: unified.CategoryReliability,
ResourceID: "patrol-runtime",
ResourceName: "Pulse Patrol Service",
ResourceType: "service",
Title: "Pulse Patrol: Provider connection issue",
Description: "Pulse Patrol could not maintain a healthy connection to the configured provider during analysis.",
Impact: "While Patrol cannot analyze, alerts continue to fire without evidence or recommended actions, and AI Intelligence summaries cannot refresh.",
Recommendation: "Check provider reachability, base URL, firewall or proxy rules, and provider availability, then rerun Patrol.",
DetectedAt: now,
LastSeenAt: now,
}
got, err := json.Marshal(finding)
if err != nil {
t.Fatalf("marshal unified finding: %v", err)
}
if !strings.Contains(string(got), `"impact":"While Patrol cannot analyze`) {
t.Fatalf("expected impact field in canonical UnifiedFinding payload, got %s", got)
}
}
func TestContract_ApprovalJSONSnapshot(t *testing.T) {
now := time.Date(2026, 2, 8, 13, 14, 15, 0, time.UTC)
expires := now.Add(5 * time.Minute)
decided := now.Add(2 * time.Minute)
payload := approval.ApprovalRequest{
ID: "approval-1",
ExecutionID: "exec-1",
ToolID: "tool-1",
Command: "rm -rf /tmp/cache",
TargetType: "agent",
TargetID: "host-1",
TargetName: "alpha",
Context: "Cleanup temporary cache",
RequestedBy: approval.RequesterPulsePatrol,
RiskLevel: approval.RiskHigh,
Status: approval.StatusApproved,
RequestedAt: now,
ExpiresAt: expires,
DecidedAt: &decided,
DecidedBy: "admin",
DenyReason: "not needed",
CommandHash: "sha256:abc",
Consumed: true,
}
got, err := json.Marshal(payload)
if err != nil {
t.Fatalf("marshal approval: %v", err)
}
const want = `{
"id":"approval-1",
"executionId":"exec-1",
"toolId":"tool-1",
"command":"rm -rf /tmp/cache",
"targetType":"agent",
"targetId":"host-1",
"targetName":"alpha",
"context":"Cleanup temporary cache",
"requestedBy":"pulse_patrol",
"riskLevel":"high",
"status":"approved",
"requestedAt":"2026-02-08T13:14:15Z",
"expiresAt":"2026-02-08T13:19:15Z",
"decidedAt":"2026-02-08T13:16:15Z",
"decidedBy":"admin",
"denyReason":"not needed",
"commandHash":"sha256:abc",
"consumed":true
}`
assertJSONSnapshot(t, got, want)
}
func TestContract_ApprovalListResponseJSONSnapshot(t *testing.T) {
previousStore := approval.GetStore()
approval.SetStore(nil)
t.Cleanup(func() { approval.SetStore(previousStore) })
handler := newTestAISettingsHandler(&config.Config{DataPath: t.TempDir()}, nil, nil)
req := httptest.NewRequest(http.MethodGet, "/api/ai/approvals", nil)
rec := httptest.NewRecorder()
handler.HandleListApprovals(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("approval list status = %d, want %d: %s", rec.Code, http.StatusOK, rec.Body.String())
}
const want = `{
"approvals":[],
"stats":{
"approved":0,
"denied":0,
"executions":0,
"expired":0,
"pending":0
}
}`
assertJSONSnapshot(t, rec.Body.Bytes(), want)
}
func TestContract_HostedSignupResponseJSONSnapshot(t *testing.T) {
payload := hostedSignupResponse{
Message: hostedSignupAcceptedMessage,
}
got, err := json.Marshal(payload)
if err != nil {
t.Fatalf("marshal hosted signup response: %v", err)
}
if strings.Contains(string(got), "user_id") || strings.Contains(string(got), "org_id") {
t.Fatalf("hosted signup accepted response must not expose identity fields: %s", got)
}
const want = `{
"message":"If that email can finish signup, you'll receive a Pulse Account sign-in link shortly."
}`
assertJSONSnapshot(t, got, want)
}
func TestContract_BillingStateQuickstartJSONSnapshot(t *testing.T) {
grantedAt := time.Date(2026, 3, 25, 14, 30, 0, 0, time.UTC).Unix()
payload := billingState{
Capabilities: []string{"ai_autofix", "ai_patrol"},
Limits: map[string]int64{"max_monitored_systems": 25},
MetersEnabled: []string{},
PlanVersion: "cloud_starter",
SubscriptionState: subscriptionStateActiveValue,
QuickstartCreditsGranted: true,
QuickstartCreditsUsed: 3,
QuickstartCreditsGrantedAt: &grantedAt,
}
got, err := json.Marshal(payload)
if err != nil {
t.Fatalf("marshal billing state: %v", err)
}
const want = `{
"capabilities":["ai_autofix","ai_patrol"],
"limits":{"max_monitored_systems":25},
"meters_enabled":[],
"plan_version":"cloud_starter",
"subscription_state":"active",
"quickstart_credits_granted":true,
"quickstart_credits_used":3,
"quickstart_credits_granted_at":1774449000
}`
assertJSONSnapshot(t, got, want)
}
func TestContract_HostedAISettingsDoesNotAutoBootstrapQuickstartJSONSnapshot(t *testing.T) {
t.Setenv("PULSE_HOSTED_MODE", "true")
baseDir := t.TempDir()
mtp := config.NewMultiTenantPersistence(baseDir)
persistence, err := mtp.GetPersistence("default")
if err != nil {
t.Fatalf("GetPersistence(default): %v", err)
}
seedHostedAIBillingState(t, mtp, "default")
handler := NewAISettingsHandler(mtp, nil, nil)
handler.defaultConfig = &config.Config{DataPath: baseDir}
req := httptest.NewRequest(http.MethodGet, "/api/settings/ai", nil)
rec := httptest.NewRecorder()
handler.HandleGetAISettings(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("status=%d, want %d: %s", rec.Code, http.StatusOK, rec.Body.String())
}
if persistence.HasAIConfig() {
t.Fatal("expected hosted AI settings contract not to persist implicit quickstart bootstrap")
}
const want = `{
"enabled":false,
"model":"",
"configured":false,
"custom_context":"",
"auth_method":"api_key",
"oauth_connected":false,
"patrol_interval_minutes":360,
"patrol_enabled":true,
"patrol_auto_fix":false,
"alert_triggered_analysis":true,
"patrol_event_triggers_enabled":true,
"patrol_alert_triggers_enabled":true,
"patrol_anomaly_triggers_enabled":true,
"patrol_alert_trigger_min_severity":"critical",
"patrol_alert_trigger_types":[],
"use_proactive_thresholds":false,
"available_models":[],
"anthropic_configured":false,
"openai_configured":false,
"openrouter_configured":false,
"deepseek_configured":false,
"gemini_configured":false,
"zai_configured":false,
"groq_configured":false,
"mistral_configured":false,
"cerebras_configured":false,
"together_configured":false,
"fireworks_configured":false,
"ollama_configured":false,
"ollama_base_url":"http://localhost:11434",
"ollama_password_set":false,
"ollama_keep_alive":"30s",
"configured_providers":[],
"control_level":"read_only",
"protected_guests":[],
"discovery_enabled":false,
"patrol_readiness":{"status":"not_ready","ready":false,"cause":"service_unavailable","summary":"Pulse Patrol service is not available.","checks":[{"id":"service","status":"not_ready","cause":"service_unavailable","label":"Patrol service","message":"Pulse Patrol service is not available.","action":"restart_service"}]}
}`
assertJSONSnapshot(t, rec.Body.Bytes(), want, "providers")
}
func TestContract_AISettingsRetiredQuickstartAliasJSONSnapshot(t *testing.T) {
tmp := t.TempDir()
cfg := &config.Config{DataPath: tmp}
persistence := config.NewConfigPersistence(tmp)
aiCfg := config.NewDefaultAIConfig()
aiCfg.Enabled = true
aiCfg.Model = "quickstart:minimax-2.5m"
aiCfg.ChatModel = "quickstart:minimax-2.5m"
aiCfg.PatrolModel = "quickstart:minimax-2.5m"
aiCfg.DiscoveryModel = "quickstart:minimax-2.5m"
aiCfg.AutoFixModel = "quickstart:minimax-2.5m"
if err := persistence.SaveAIConfig(*aiCfg); err != nil {
t.Fatalf("SaveAIConfig: %v", err)
}
handler := newTestAISettingsHandler(cfg, persistence, nil)
req := httptest.NewRequest(http.MethodGet, "/api/settings/ai", nil)
rec := httptest.NewRecorder()
handler.HandleGetAISettings(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("status=%d, want %d: %s", rec.Code, http.StatusOK, rec.Body.String())
}
const want = `{
"enabled":true,
"model":"",
"configured":false,
"custom_context":"",
"auth_method":"api_key",
"oauth_connected":false,
"patrol_interval_minutes":360,
"patrol_enabled":true,
"patrol_auto_fix":false,
"alert_triggered_analysis":true,
"patrol_event_triggers_enabled":true,
"patrol_alert_triggers_enabled":true,
"patrol_anomaly_triggers_enabled":true,
"patrol_alert_trigger_min_severity":"critical",
"patrol_alert_trigger_types":[],
"use_proactive_thresholds":false,
"available_models":[],
"anthropic_configured":false,
"openai_configured":false,
"openrouter_configured":false,
"deepseek_configured":false,
"gemini_configured":false,
"zai_configured":false,
"groq_configured":false,
"mistral_configured":false,
"cerebras_configured":false,
"together_configured":false,
"fireworks_configured":false,
"ollama_configured":false,
"ollama_base_url":"http://localhost:11434",
"ollama_password_set":false,
"ollama_keep_alive":"30s",
"configured_providers":[],
"control_level":"read_only",
"protected_guests":[],
"discovery_enabled":false,
"patrol_readiness":{"status":"not_ready","ready":false,"cause":"service_unavailable","summary":"Pulse Patrol service is not available.","checks":[{"id":"service","status":"not_ready","cause":"service_unavailable","label":"Patrol service","message":"Pulse Patrol service is not available.","action":"restart_service"}]}
}`
assertJSONSnapshot(t, rec.Body.Bytes(), want, "providers")
if bytes.Contains(bytes.ToLower(rec.Body.Bytes()), []byte("quickstart")) {
t.Fatalf("expected AI settings payload to suppress legacy hosted quickstart aliases, got %s", rec.Body.Bytes())
}
}
func TestContract_AISettingsOllamaAuthJSONSnapshot(t *testing.T) {
tmp := t.TempDir()
cfg := &config.Config{DataPath: tmp}
persistence := config.NewConfigPersistence(tmp)
aiCfg := config.NewDefaultAIConfig()
aiCfg.Enabled = true
aiCfg.Model = "ollama:llama3"
aiCfg.PatrolModel = "ollama:llama3"
aiCfg.OllamaBaseURL = "http://ollama.example:11434"
aiCfg.OllamaUsername = "unai"
aiCfg.OllamaPassword = "secret"
if err := persistence.SaveAIConfig(*aiCfg); err != nil {
t.Fatalf("SaveAIConfig: %v", err)
}
handler := newTestAISettingsHandler(cfg, persistence, nil)
req := httptest.NewRequest(http.MethodGet, "/api/settings/ai", nil)
rec := httptest.NewRecorder()
handler.HandleGetAISettings(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("status=%d, want %d: %s", rec.Code, http.StatusOK, rec.Body.String())
}
const want = `{
"enabled":true,
"model":"ollama:llama3",
"patrol_model":"ollama:llama3",
"configured":true,
"custom_context":"",
"auth_method":"api_key",
"oauth_connected":false,
"patrol_interval_minutes":360,
"patrol_enabled":true,
"patrol_auto_fix":false,
"alert_triggered_analysis":true,
"patrol_event_triggers_enabled":true,
"patrol_alert_triggers_enabled":true,
"patrol_anomaly_triggers_enabled":true,
"patrol_alert_trigger_min_severity":"critical",
"patrol_alert_trigger_types":[],
"use_proactive_thresholds":false,
"available_models":[],
"anthropic_configured":false,
"openai_configured":false,
"openrouter_configured":false,
"deepseek_configured":false,
"gemini_configured":false,
"zai_configured":false,
"groq_configured":false,
"mistral_configured":false,
"cerebras_configured":false,
"together_configured":false,
"fireworks_configured":false,
"ollama_configured":true,
"ollama_base_url":"http://ollama.example:11434",
"ollama_username":"unai",
"ollama_password_set":true,
"ollama_keep_alive":"30s",
"configured_providers":["ollama"],
"control_level":"read_only",
"protected_guests":[],
"discovery_enabled":false,
"patrol_readiness":{"status":"not_ready","ready":false,"cause":"service_unavailable","summary":"Pulse Patrol service is not available.","checks":[{"id":"service","status":"not_ready","cause":"service_unavailable","label":"Patrol service","message":"Pulse Patrol service is not available.","action":"restart_service"}]}
}`
assertJSONSnapshot(t, rec.Body.Bytes(), want, "providers")
}
func TestContract_VMInventoryExportCSVHeaders(t *testing.T) {
handler := NewReportingHandlers(nil, nil)
req := httptest.NewRequest(http.MethodGet, "/api/admin/reports/inventory/vms/export?format=csv", nil)
rec := httptest.NewRecorder()
handler.HandleExportVMInventory(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("expected 200, got %d body=%s", rec.Code, rec.Body.String())
}
if got := rec.Header().Get("Content-Type"); got != "text/csv; charset=utf-8" {
t.Fatalf("expected csv content type, got %q", got)
}
if got := rec.Header().Get("Content-Disposition"); !strings.HasPrefix(got, "attachment; filename=\"vm-inventory-") {
t.Fatalf("expected VM inventory attachment filename, got %q", got)
}
const want = "Resource ID,Instance,Node,Pool,VMID,VM Name,Status,CPU Cores,Memory Allocated Bytes,Disk Allocated Bytes,Disk Used Bytes,Disk Status Reason\n"
if got := rec.Body.String(); got != want {
t.Fatalf("unexpected VM inventory CSV header row:\nwant %q\ngot %q", want, got)
}
}
func TestContract_ReportingCatalogJSONSnapshot(t *testing.T) {
handler := NewReportingHandlers(nil, nil)
req := httptest.NewRequest(http.MethodGet, "/api/admin/reports/catalog", nil)
rec := httptest.NewRecorder()
handler.HandleGetReportingCatalog(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("expected 200, got %d body=%s", rec.Code, rec.Body.String())
}
if got := rec.Header().Get("Content-Type"); got != "application/json" {
t.Fatalf("expected json content type, got %q", got)
}
const want = `{
"id":"advanced_reporting",
"title":"Detailed Reporting",
"description":"Generate performance reports and current-state exports across infrastructure and workloads.",
"lockedState":{
"title":"Advanced Reporting",
"description":"AI-narrated performance reports for a single resource or your full fleet, with an executive summary, outlier callouts, and period-over-period changes. PDF and CSV formats, plus current-state VM inventory exports. AI narration uses Pulse Assistant when configured; reports fall back to a deterministic summary otherwise. Available on paid self-hosted and hosted plans."
},
"guidance":{
"title":"Advanced Insights",
"description":"Performance reports include an AI-narrated executive summary, fleet outliers, and period-over-period comparison. They draw from the historical metrics store and Patrol findings within the window. The VM inventory export captures current runtime state for spreadsheet-friendly fleet reviews. Use reports for retrospective trends and the inventory export for current allocation snapshots."
},
"performanceReport":{
"id":"performance_reports",
"title":"Performance Reports",
"description":"Generate PDF summaries or CSV metric exports from historical monitoring data for one or more selected resources.",
"singleResourceEndpoint":"/api/admin/reports/generate",
"multiResourceEndpoint":"/api/admin/reports/generate-multi",
"singleFilenamePrefix":"report",
"singleFilenameSubject":"resource_id",
"multiFilenamePrefix":"fleet-report",
"filenameDateStyle":"utc_yyyymmdd",
"formats":[
{
"value":"pdf",
"label":"PDF Report"
},
{
"value":"csv",
"label":"CSV Data"
}
],
"defaultFormat":"pdf",
"ranges":[
{
"key":"24h",
"label":"Last 24 Hours",
"description":"Current-day operational summary for short-term regressions.",
"windowHours":24
},
{
"key":"7d",
"label":"Last 7 Days",
"description":"Weekly trend window for recent performance changes.",
"windowHours":168
},
{
"key":"30d",
"label":"Last 30 Days",
"description":"Monthly review window for sustained capacity or reliability shifts.",
"windowHours":720
}
],
"defaultRange":"24h",
"multiResourceMax":50,
"supportsMetricFilter":true,
"supportsCustomTitle":true
},
"vmInventoryExport":{
"id":"vm_inventory",
"title":"VM Inventory Export",
"description":"Export the current fleet-wide VM inventory as CSV using the canonical runtime model. Includes VM identity, placement, CPU, memory allocation, disk allocation, and disk usage columns.",
"format":"csv",
"exportEndpoint":"/api/admin/reports/inventory/vms/export",
"filenamePrefix":"vm-inventory",
"filenameDateStyle":"utc_yyyymmdd",
"columns":[
{
"key":"resource_id",
"label":"Resource ID",
"description":"Canonical Pulse resource ID for the VM."
},
{
"key":"instance",
"label":"Instance",
"description":"Configured Proxmox instance or cluster name."
},
{
"key":"node",
"label":"Node",
"description":"Proxmox node currently hosting the VM."
},
{
"key":"pool",
"label":"Pool",
"description":"Canonical Proxmox pool membership when the platform reports one."
},
{
"key":"vmid",
"label":"VMID",
"description":"Numeric Proxmox VM identifier."
},
{
"key":"vm_name",
"label":"VM Name",
"description":"Current VM display name from the runtime model."
},
{
"key":"status",
"label":"Status",
"description":"Canonical runtime status for the VM."
},
{
"key":"cpu_cores",
"label":"CPU Cores",
"description":"Allocated virtual CPU core count."
},
{
"key":"memory_allocated_bytes",
"label":"Memory Allocated Bytes",
"description":"Configured memory allocation in bytes."
},
{
"key":"disk_allocated_bytes",
"label":"Disk Allocated Bytes",
"description":"Total allocated disk capacity in bytes across the VM."
},
{
"key":"disk_used_bytes",
"label":"Disk Used Bytes",
"description":"Current used disk bytes from the canonical runtime disk view."
},
{
"key":"disk_status_reason",
"label":"Disk Status Reason",
"description":"Reason disk usage is partial or unavailable when the runtime cannot provide a full guest view."
}
]
}
}`
assertJSONSnapshot(t, rec.Body.Bytes(), want)
}
func TestContract_PerformanceReportTransportUsesCatalogDefinition(t *testing.T) {
engine := &stubReportingEngine{data: []byte("report"), contentType: "application/pdf"}
original := reporting.GetEngine()
reporting.SetEngine(engine)
t.Cleanup(func() { reporting.SetEngine(original) })
handler := NewReportingHandlers(nil, nil)
req := httptest.NewRequest(
http.MethodGet,
"/api/reporting?resourceType=node&resourceId=node-1&metricType=+cpu+&title=+Node+report+",
nil,
)
rec := httptest.NewRecorder()
handler.HandleGenerateReport(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("expected 200, got %d body=%s", rec.Code, rec.Body.String())
}
definition := reporting.DescribePerformanceReport()
if got := rec.Header().Get("Content-Disposition"); !strings.HasPrefix(got, fmt.Sprintf("attachment; filename=\"%s-node-1-", definition.SingleFilenamePrefix)) {
t.Fatalf("expected canonical performance-report attachment filename, got %q", got)
}
if engine.lastReq.Format != definition.DefaultFormat {
t.Fatalf("expected default format %q, got %q", definition.DefaultFormat, engine.lastReq.Format)
}
if engine.lastReq.MetricType != "cpu" || engine.lastReq.Title != "Node report" {
t.Fatalf("expected trimmed canonical optional fields, got %+v", engine.lastReq)
}
}
func TestContract_ReportingRequestCarriesEntitledReportBranding(t *testing.T) {
engine := &stubReportingEngine{data: []byte("report"), contentType: "application/pdf"}
original := reporting.GetEngine()
reporting.SetEngine(engine)
t.Cleanup(func() { reporting.SetEngine(original) })
service := pkglicensing.NewService()
service.SetCurrentForTesting(&pkglicensing.License{
Claims: pkglicensing.Claims{
LicenseID: "lic_contract_report_branding",
Email: "brand@example.test",
Tier: pkglicensing.TierEnterprise,
},
ValidatedAt: time.Now(),
})
SetLicenseServiceProvider(reportBrandLicenseProvider{service: service})
t.Cleanup(func() { SetLicenseServiceProvider(nil) })
t.Setenv("PULSE_REPORT_PROVIDER_BRAND_DISPLAY_NAME", "Provider Default")
handler := NewReportingHandlers(nil, nil)
handler.SetSystemSettingsStore(reportBrandSettingsStore{settings: &config.SystemSettings{
ReportBranding: &config.ReportBrandSettings{DisplayName: "Client One"},
}})
req := httptest.NewRequest(http.MethodGet, "/api/reporting?resourceType=node&resourceId=node-1", nil)
rec := httptest.NewRecorder()
handler.HandleGenerateReport(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("expected 200, got %d body=%s", rec.Code, rec.Body.String())
}
if !engine.lastReq.Branding.Entitled {
t.Fatal("expected reporting request to carry white_label entitlement state")
}
if got := engine.lastReq.Branding.ProviderDefault.DisplayName; got != "Provider Default" {
t.Fatalf("provider brand = %q, want Provider Default", got)
}
if got := engine.lastReq.Branding.WorkspaceOverride.DisplayName; got != "Client One" {
t.Fatalf("workspace brand = %q, want Client One", got)
}
if got := engine.lastReq.Branding.EffectiveBrand(); got == nil || got.DisplayName != "Client One" {
t.Fatalf("effective brand should prefer workspace override, got %+v", got)
}
}
func TestContract_ReportBrandingSettingsRejectWorkspaceLogoPath(t *testing.T) {
err := validateReportBrandingSettings(map[string]interface{}{
"displayName": "Client One",
"logoPath": "/etc/pulse/secrets/handoff.key",
})
if err == nil || !strings.Contains(err.Error(), "reportBranding.logoPath is not supported") {
t.Fatalf("expected workspace logoPath to be rejected, got %v", err)
}
err = validateReportBrandingSettings(map[string]interface{}{
"displayName": "Client One",
"logoBase64": "iVBORw0KGgo=",
"logoFormat": "png",
})
if err != nil {
t.Fatalf("expected inline report logo settings to remain valid, got %v", err)
}
}
func TestContract_ReportingCatalogRouteAccessibleWithoutReportingFeature(t *testing.T) {
rawToken := "reporting-catalog-contract-token-123.12345678"
record := newTokenRecord(t, rawToken, []string{config.ScopeSettingsRead}, nil)
cfg := newTestConfigWithTokens(t, record)
router := NewRouter(cfg, nil, nil, nil, nil, "1.0.0")
req := httptest.NewRequest(http.MethodGet, "/api/admin/reports/catalog", nil)
req.Header.Set("X-API-Token", rawToken)
rec := httptest.NewRecorder()
router.Handler().ServeHTTP(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("expected 200 for ungated reporting catalog route, got %d body=%s", rec.Code, rec.Body.String())
}
if got := rec.Header().Get("Content-Type"); !strings.Contains(got, "application/json") {
t.Fatalf("expected json content type, got %q", got)
}
}
func TestContract_LocalCommercialAnalyticsRoutesAreNotRegistered(t *testing.T) {
rawToken := "retired-commercial-analytics-contract-token-123.12345678"
record := newTokenRecord(t, rawToken, []string{config.ScopeSettingsRead}, nil)
cfg := newTestConfigWithTokens(t, record)
router := NewRouter(cfg, nil, nil, nil, nil, "1.0.0")
for _, tc := range []struct {
method string
path string
}{
{method: http.MethodPost, path: "/api/upgrade-metrics/events"},
{method: http.MethodGet, path: "/api/upgrade-metrics/stats"},
{method: http.MethodGet, path: "/api/upgrade-metrics/health"},
{method: http.MethodGet, path: "/api/upgrade-metrics/config"},
{method: http.MethodPut, path: "/api/upgrade-metrics/config"},
{method: http.MethodGet, path: "/api/admin/upgrade-metrics-funnel"},
} {
t.Run(tc.method+" "+tc.path, func(t *testing.T) {
req := httptest.NewRequest(tc.method, tc.path, nil)
req.Header.Set("X-API-Token", rawToken)
rec := httptest.NewRecorder()
router.Handler().ServeHTTP(rec, req)
if rec.Code != http.StatusNotFound {
t.Fatalf("%s %s status=%d, want %d body=%s", tc.method, tc.path, rec.Code, http.StatusNotFound, rec.Body.String())
}
})
}
}
func TestContract_PerformanceReportTransportUsesCatalogDefaultRange(t *testing.T) {
engine := &stubReportingEngine{data: []byte("report"), contentType: "application/pdf"}
original := reporting.GetEngine()
reporting.SetEngine(engine)
t.Cleanup(func() { reporting.SetEngine(original) })
handler := NewReportingHandlers(nil, nil)
end := time.Date(2026, 3, 25, 15, 0, 0, 0, time.UTC)
req := httptest.NewRequest(
http.MethodGet,
"/api/reporting?resourceType=node&resourceId=node-1&end="+url.QueryEscape(end.Format(time.RFC3339)),
nil,
)
rec := httptest.NewRecorder()
handler.HandleGenerateReport(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("expected 200, got %d body=%s", rec.Code, rec.Body.String())
}
definition := reporting.DescribePerformanceReport()
if got := engine.lastReq.Start; !got.Equal(end.Add(-definition.DefaultRangeDuration())) {
t.Fatalf("expected canonical default start time, got %s", got)
}
if !engine.lastReq.End.Equal(end) {
t.Fatalf("expected canonical end time, got %s", engine.lastReq.End)
}
}
func TestContract_PerformanceReportTransportRejectsInvalidTimeRange(t *testing.T) {
engine := &stubReportingEngine{data: []byte("report"), contentType: "application/pdf"}
original := reporting.GetEngine()
reporting.SetEngine(engine)
t.Cleanup(func() { reporting.SetEngine(original) })
handler := NewReportingHandlers(nil, nil)
req := httptest.NewRequest(
http.MethodGet,
"/api/reporting?resourceType=node&resourceId=node-1&start=2026-03-25T12:00:00Z&end=2026-03-25T11:00:00Z",
nil,
)
rec := httptest.NewRecorder()
handler.HandleGenerateReport(rec, req)
if rec.Code != http.StatusBadRequest {
t.Fatalf("expected 400, got %d body=%s", rec.Code, rec.Body.String())
}
var payload struct {
Code string `json:"code"`
Error string `json:"error"`
}
if err := json.NewDecoder(rec.Body).Decode(&payload); err != nil {
t.Fatalf("decode invalid-time-range response: %v", err)
}
if payload.Code != "invalid_time_range" {
t.Fatalf("expected invalid_time_range code, got %q", payload.Code)
}
if payload.Error != "end must be after start" {
t.Fatalf("expected canonical invalid_time_range message, got %q", payload.Error)
}
}
func TestContract_PerformanceReportTransportRejectsOversizedMultiBody(t *testing.T) {
engine := &stubReportingEngine{data: []byte("report"), contentType: "application/pdf"}
original := reporting.GetEngine()
reporting.SetEngine(engine)
t.Cleanup(func() { reporting.SetEngine(original) })
handler := NewReportingHandlers(nil, nil)
padding := strings.Repeat("x", reportingMultiReportBodyMax)
req := httptest.NewRequest(
http.MethodPost,
"/api/reporting/generate-multi",
strings.NewReader(fmt.Sprintf(`{"resources":[{"resourceType":"vm","resourceId":"vm-1"}],"format":"pdf","title":"%s"}`, padding)),
)
rec := httptest.NewRecorder()
handler.HandleGenerateMultiReport(rec, req)
if rec.Code != http.StatusBadRequest {
t.Fatalf("expected 400, got %d body=%s", rec.Code, rec.Body.String())
}
var payload struct {
Code string `json:"code"`
Error string `json:"error"`
}
if err := json.NewDecoder(rec.Body).Decode(&payload); err != nil {
t.Fatalf("decode oversized-body response: %v", err)
}
if payload.Code != "body_too_large" {
t.Fatalf("expected body_too_large code, got %q", payload.Code)
}
if payload.Error != "Request body must be 1MB or less" {
t.Fatalf("expected canonical oversized-body message, got %q", payload.Error)
}
}
func TestContract_PerformanceReportTransportRejectsInvalidOptionalFieldWithStableCode(t *testing.T) {
engine := &stubReportingEngine{data: []byte("report"), contentType: "application/pdf"}
original := reporting.GetEngine()
reporting.SetEngine(engine)
t.Cleanup(func() { reporting.SetEngine(original) })
handler := NewReportingHandlers(nil, nil)
req := httptest.NewRequest(
http.MethodGet,
"/api/reporting?resourceType=node&resourceId=node-1&metricType="+url.QueryEscape("cpu usage"),
nil,
)
rec := httptest.NewRecorder()
handler.HandleGenerateReport(rec, req)
if rec.Code != http.StatusBadRequest {
t.Fatalf("expected 400, got %d body=%s", rec.Code, rec.Body.String())
}
var payload struct {
Code string `json:"code"`
Error string `json:"error"`
}
if err := json.NewDecoder(rec.Body).Decode(&payload); err != nil {
t.Fatalf("decode invalid optional-field response: %v", err)
}
if payload.Code != "invalid_metric_type" {
t.Fatalf("expected invalid_metric_type code, got %q", payload.Code)
}
if payload.Error != "metricType must match [a-zA-Z0-9._:-]+ and be <= 64 chars" {
t.Fatalf("expected canonical invalid_metric_type message, got %q", payload.Error)
}
}
func TestContract_ReportingInvalidFormatErrorsUseCatalogDefinitions(t *testing.T) {
engine := &stubReportingEngine{data: []byte("report"), contentType: "application/pdf"}
original := reporting.GetEngine()
reporting.SetEngine(engine)
t.Cleanup(func() { reporting.SetEngine(original) })
handler := NewReportingHandlers(nil, nil)
reportReq := httptest.NewRequest(
http.MethodGet,
"/api/reporting?format=xlsx&resourceType=node&resourceId=node-1",
nil,
)
reportRec := httptest.NewRecorder()
handler.HandleGenerateReport(reportRec, reportReq)
if reportRec.Code != http.StatusBadRequest {
t.Fatalf("expected 400 for invalid performance report format, got %d body=%s", reportRec.Code, reportRec.Body.String())
}
var reportPayload struct {
Error string `json:"error"`
}
if err := json.NewDecoder(reportRec.Body).Decode(&reportPayload); err != nil {
t.Fatalf("decode performance invalid-format response: %v", err)
}
if reportPayload.Error != reporting.DescribePerformanceReport().InvalidFormatError() {
t.Fatalf("expected canonical performance invalid-format error, got %q", reportPayload.Error)
}
inventoryReq := httptest.NewRequest(
http.MethodGet,
"/api/admin/reports/inventory/vms/export?format=pdf",
nil,
)
inventoryRec := httptest.NewRecorder()
handler.HandleExportVMInventory(inventoryRec, inventoryReq)
if inventoryRec.Code != http.StatusBadRequest {
t.Fatalf("expected 400 for invalid inventory format, got %d body=%s", inventoryRec.Code, inventoryRec.Body.String())
}
var inventoryPayload struct {
Error string `json:"error"`
}
if err := json.NewDecoder(inventoryRec.Body).Decode(&inventoryPayload); err != nil {
t.Fatalf("decode inventory invalid-format response: %v", err)
}
if inventoryPayload.Error != reporting.DescribeVMInventoryExport().InvalidFormatError() {
t.Fatalf("expected canonical inventory invalid-format error, got %q", inventoryPayload.Error)
}
}
func TestContract_HostedTenantAISettingsDoesNotAutoBootstrapQuickstartJSONSnapshot(t *testing.T) {
t.Setenv("PULSE_HOSTED_MODE", "true")
baseDir := t.TempDir()
mtp := config.NewMultiTenantPersistence(baseDir)
persistence, err := mtp.GetPersistence("t-tenant")
if err != nil {
t.Fatalf("GetPersistence(t-tenant): %v", err)
}
seedHostedAIBillingState(t, mtp, "default")
handler := NewAISettingsHandler(mtp, nil, nil)
handler.defaultConfig = &config.Config{DataPath: baseDir}
req := httptest.NewRequest(http.MethodGet, "/api/settings/ai", nil)
req = req.WithContext(context.WithValue(req.Context(), OrgIDContextKey, "t-tenant"))
rec := httptest.NewRecorder()
handler.HandleGetAISettings(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("status=%d, want %d: %s", rec.Code, http.StatusOK, rec.Body.String())
}
if persistence.HasAIConfig() {
t.Fatal("expected hosted tenant AI settings contract not to persist implicit quickstart bootstrap")
}
const want = `{
"enabled":false,
"model":"",
"configured":false,
"custom_context":"",
"auth_method":"api_key",
"oauth_connected":false,
"patrol_interval_minutes":360,
"patrol_enabled":true,
"patrol_auto_fix":false,
"alert_triggered_analysis":true,
"patrol_event_triggers_enabled":true,
"patrol_alert_triggers_enabled":true,
"patrol_anomaly_triggers_enabled":true,
"patrol_alert_trigger_min_severity":"critical",
"patrol_alert_trigger_types":[],
"use_proactive_thresholds":false,
"available_models":[],
"anthropic_configured":false,
"openai_configured":false,
"openrouter_configured":false,
"deepseek_configured":false,
"gemini_configured":false,
"zai_configured":false,
"groq_configured":false,
"mistral_configured":false,
"cerebras_configured":false,
"together_configured":false,
"fireworks_configured":false,
"ollama_configured":false,
"ollama_base_url":"http://localhost:11434",
"ollama_password_set":false,
"ollama_keep_alive":"30s",
"configured_providers":[],
"control_level":"read_only",
"protected_guests":[],
"discovery_enabled":false,
"patrol_readiness":{"status":"not_ready","ready":false,"cause":"service_unavailable","summary":"Pulse Patrol service is not available.","checks":[{"id":"service","status":"not_ready","cause":"service_unavailable","label":"Patrol service","message":"Pulse Patrol service is not available.","action":"restart_service"}]}
}`
assertJSONSnapshot(t, rec.Body.Bytes(), want, "providers")
}
func TestContract_StripeWebhookHandlersUseCanonicalRuntimeDataDir(t *testing.T) {
envDir := t.TempDir()
t.Setenv("PULSE_DATA_DIR", envDir)
persistence := config.NewMultiTenantPersistence(envDir)
billingStore := config.NewFileBillingStore(envDir)
rbacProvider := NewTenantRBACProvider(envDir)
withExplicitDir := NewStripeWebhookHandlers(billingStore, persistence, rbacProvider, nil, nil, true, envDir)
if got := filepath.Dir(withExplicitDir.deduper.dir); got != filepath.Join(envDir, "stripe") {
t.Fatalf("explicit dedupe dir root = %q, want %q", got, filepath.Join(envDir, "stripe"))
}
if got := filepath.Dir(withExplicitDir.index.dir); got != filepath.Join(envDir, "stripe") {
t.Fatalf("explicit customer index dir root = %q, want %q", got, filepath.Join(envDir, "stripe"))
}
withEnvFallback := NewStripeWebhookHandlers(billingStore, persistence, rbacProvider, nil, nil, true, "")
if got := filepath.Dir(withEnvFallback.deduper.dir); got != filepath.Join(envDir, "stripe") {
t.Fatalf("env fallback dedupe dir root = %q, want %q", got, filepath.Join(envDir, "stripe"))
}
if got := filepath.Dir(withEnvFallback.index.dir); got != filepath.Join(envDir, "stripe") {
t.Fatalf("env fallback customer index dir root = %q, want %q", got, filepath.Join(envDir, "stripe"))
}
}
func TestContract_NotificationWebhookTestResponseJSONSnapshot(t *testing.T) {
payload := map[string]interface{}{
"success": true,
"status": 200,
"response": "OK",
}
got, err := json.Marshal(payload)
if err != nil {
t.Fatalf("marshal webhook test response: %v", err)
}
const want = `{
"response":"OK",
"status":200,
"success":true
}`
assertJSONSnapshot(t, got, want)
}
func TestContract_NotificationPushoverWebhookResponseJSONSnapshot(t *testing.T) {
payload := map[string]interface{}{
"id": "hook-1",
"name": "Pushover",
"url": "https://api.pushover.net/1/messages.json",
"service": "pushover",
"customFields": map[string]string{
"token": "app-token",
"user": "user-key",
},
}
got, err := json.Marshal(payload)
if err != nil {
t.Fatalf("marshal pushover webhook response: %v", err)
}
const want = `{
"customFields":{"token":"app-token","user":"user-key"},
"id":"hook-1",
"name":"Pushover",
"service":"pushover",
"url":"https://api.pushover.net/1/messages.json"
}`
assertJSONSnapshot(t, got, want)
}
func TestContract_ResourceIntelligenceIncludesRecentChanges(t *testing.T) {
svc := newEnabledAIService(t)
canonicalStore := unifiedresources.NewMemoryStore()
observedAt := time.Now().Add(-15 * time.Minute)
if err := canonicalStore.RecordChange(unifiedresources.ResourceChange{
ID: "change-contract",
ObservedAt: observedAt,
ResourceID: "vm-100",
Kind: unifiedresources.ChangeRestart,
SourceType: unifiedresources.SourcePlatformEvent,
Reason: "guest restarted",
}); err != nil {
t.Fatalf("record canonical change: %v", err)
}
correlationDetector := correlation.NewDetector(correlation.Config{
MinOccurrences: 1,
CorrelationWindow: 2 * time.Hour,
RetentionWindow: 24 * time.Hour,
})
correlationBase := observedAt.Add(-10 * time.Minute)
correlationDetector.RecordEvent(correlation.Event{
ResourceID: "storage-1",
ResourceName: "storage-1",
ResourceType: "storage",
EventType: correlation.EventDiskFull,
Timestamp: correlationBase,
})
correlationDetector.RecordEvent(correlation.Event{
ResourceID: "vm-100",
ResourceName: "vm-100",
ResourceType: "vm",
EventType: correlation.EventRestart,
Timestamp: correlationBase.Add(1 * time.Minute),
})
correlationDetector.RecordEvent(correlation.Event{
ResourceID: "storage-1",
ResourceName: "storage-1",
ResourceType: "storage",
EventType: correlation.EventDiskFull,
Timestamp: correlationBase.Add(2 * time.Minute),
})
correlationDetector.RecordEvent(correlation.Event{
ResourceID: "vm-100",
ResourceName: "vm-100",
ResourceType: "vm",
EventType: correlation.EventRestart,
Timestamp: correlationBase.Add(3 * time.Minute),
})
svc.SetUnifiedResourceProvider(&stubUnifiedResourceProvider{
resources: []unifiedresources.Resource{
{ID: "public-1", Type: unifiedresources.ResourceTypeVM, Tags: []string{"public"}},
{ID: "internal-1", Type: unifiedresources.ResourceTypeAgent, Agent: &unifiedresources.AgentData{Hostname: "agent-1"}},
},
})
setUnexportedField(t, svc.GetPatrolService(), "correlationDetector", correlationDetector)
setUnexportedField(t, svc, "resourceExportStore", canonicalStore)
setUnexportedField(t, svc.GetPatrolService(), "aiService", svc)
handlers := &AISettingsHandler{defaultAIService: svc}
req := httptest.NewRequest(http.MethodGet, "/api/ai/intelligence?resource_id=vm-100", nil)
rec := httptest.NewRecorder()
handlers.HandleGetIntelligence(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("status = %d, want 200: %s", rec.Code, rec.Body.String())
}
var payload map[string]interface{}
if err := json.Unmarshal(rec.Body.Bytes(), &payload); err != nil {
t.Fatalf("decode response: %v", err)
}
recentChanges, ok := payload["recent_changes"].([]interface{})
if !ok {
t.Fatalf("expected recent_changes array in response, got %T", payload["recent_changes"])
}
if len(recentChanges) != 1 {
t.Fatalf("expected 1 recent change, got %d", len(recentChanges))
}
if _, ok := payload["policy_posture"]; ok {
t.Fatal("did not expect policy_posture in resource intelligence response")
}
dependencies, ok := payload["dependencies"].([]interface{})
if !ok {
t.Fatalf("expected dependencies array in response, got %T", payload["dependencies"])
}
if len(dependencies) == 0 {
t.Fatal("expected at least one dependency in response")
}
correlations, ok := payload["correlations"].([]interface{})
if !ok {
t.Fatalf("expected correlations array in response, got %T", payload["correlations"])
}
if len(correlations) == 0 {
t.Fatal("expected at least one correlation in response")
}
firstCorrelation, ok := correlations[0].(map[string]interface{})
if !ok {
t.Fatalf("expected correlation object, got %T", correlations[0])
}
if firstCorrelation["event_pattern"] == "" {
t.Fatal("expected correlation event_pattern in response")
}
if firstCorrelation["avg_delay"] == nil {
t.Fatal("expected correlation avg_delay in response")
}
if firstCorrelation["confidence"] == nil {
t.Fatal("expected correlation confidence in response")
}
}
func TestContract_IntelligenceSummaryIncludesRecentChanges(t *testing.T) {
svc := newEnabledAIService(t)
canonicalStore := unifiedresources.NewMemoryStore()
if err := canonicalStore.RecordChange(unifiedresources.ResourceChange{
ID: "change-summary",
ObservedAt: time.Now().Add(-15 * time.Minute),
ResourceID: "vm-100",
Kind: unifiedresources.ChangeRestart,
SourceType: unifiedresources.SourcePlatformEvent,
Reason: "guest restarted",
}); err != nil {
t.Fatalf("record canonical change: %v", err)
}
svc.SetUnifiedResourceProvider(&stubUnifiedResourceProvider{
resources: []unifiedresources.Resource{
{
ID: "public-1",
Name: "public-vm",
Type: unifiedresources.ResourceTypeVM,
Tags: []string{"public"},
},
{
ID: "restricted-1",
Name: "mail-gw",
Type: unifiedresources.ResourceTypePMG,
PMG: &unifiedresources.PMGData{Hostname: "mail.internal"},
},
},
})
setUnexportedField(t, svc, "resourceExportStore", canonicalStore)
setUnexportedField(t, svc.GetPatrolService(), "aiService", svc)
handlers := &AISettingsHandler{defaultAIService: svc}
req := httptest.NewRequest(http.MethodGet, "/api/ai/intelligence", nil)
rec := httptest.NewRecorder()
handlers.HandleGetIntelligence(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("status = %d, want 200: %s", rec.Code, rec.Body.String())
}
var payload map[string]interface{}
if err := json.Unmarshal(rec.Body.Bytes(), &payload); err != nil {
t.Fatalf("decode response: %v", err)
}
recentChanges, ok := payload["recent_changes"].([]interface{})
if !ok {
t.Fatalf("expected recent_changes array in response, got %T", payload["recent_changes"])
}
if len(recentChanges) != 1 {
t.Fatalf("expected 1 recent change, got %d", len(recentChanges))
}
policyPosture, ok := payload["policy_posture"].(map[string]interface{})
if !ok {
t.Fatalf("expected policy_posture object in response, got %T", payload["policy_posture"])
}
if got := int(policyPosture["total_resources"].(float64)); got != 2 {
t.Fatalf("expected total_resources=2, got %d", got)
}
}
func TestContract_RecentChangesEndpointUsesCanonicalTimeline(t *testing.T) {
svc := newEnabledAIService(t)
canonicalStore := unifiedresources.NewMemoryStore()
if err := canonicalStore.RecordChange(unifiedresources.ResourceChange{
ID: "change-canonical",
ObservedAt: time.Now().Add(-25 * time.Minute),
ResourceID: "vm-canonical",
Kind: unifiedresources.ChangeRestart,
From: "running",
To: "restarting",
SourceType: unifiedresources.SourcePlatformEvent,
SourceAdapter: unifiedresources.AdapterProxmox,
Reason: "guest restarted after maintenance",
}); err != nil {
t.Fatalf("record canonical change: %v", err)
}
svc.SetUnifiedResourceProvider(&stubUnifiedResourceProvider{
resources: []unifiedresources.Resource{
{
ID: "vm-canonical",
Name: "canonical-vm",
Type: unifiedresources.ResourceTypeVM,
},
},
})
setUnexportedField(t, svc, "resourceExportStore", canonicalStore)
setUnexportedField(t, svc.GetPatrolService(), "aiService", svc)
handlers := &AISettingsHandler{defaultAIService: svc}
req := httptest.NewRequest(http.MethodGet, "/api/ai/intelligence/changes?hours=1", nil)
rec := httptest.NewRecorder()
handlers.HandleGetRecentChanges(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("status = %d, want 200: %s", rec.Code, rec.Body.String())
}
var payload map[string]interface{}
if err := json.Unmarshal(rec.Body.Bytes(), &payload); err != nil {
t.Fatalf("decode response: %v", err)
}
changes, ok := payload["changes"].([]interface{})
if !ok {
t.Fatalf("expected changes array in response, got %T", payload["changes"])
}
if len(changes) != 1 {
t.Fatalf("expected 1 recent change, got %d", len(changes))
}
change, ok := changes[0].(map[string]interface{})
if !ok {
t.Fatalf("expected object change, got %#v", changes[0])
}
if change["resource_name"] != "canonical-vm" {
t.Fatalf("expected canonical resource name, got %#v", change["resource_name"])
}
if change["resource_type"] != string(unifiedresources.ResourceTypeVM) {
t.Fatalf("expected resource type vm, got %#v", change["resource_type"])
}
if change["change_type"] != string(unifiedresources.ChangeRestart) {
t.Fatalf("expected canonical change type, got %#v", change["change_type"])
}
if desc, ok := change["description"].(string); !ok || !strings.Contains(desc, "Restart") {
t.Fatalf("expected canonical change description, got %#v", change["description"])
}
}
func TestContract_AIIntelligenceCorrelationsJSONSnapshot(t *testing.T) {
now := time.Date(2026, 3, 18, 17, 30, 0, 0, time.UTC)
payload := map[string]any{
"correlations": []map[string]any{
{
"source_id": "node-1",
"source_name": "node-1",
"source_type": "node",
"target_id": "vm-1",
"target_name": "vm-1",
"target_type": "vm",
"event_pattern": "high_cpu -> restart",
"occurrences": 1,
"avg_delay": "1m0s",
"confidence": 0.1,
"last_seen": now,
"description": "When node-1 experiences high_cpu, vm-1 often follows within 1m0s",
},
},
"count": 1,
}
got, err := json.Marshal(payload)
if err != nil {
t.Fatalf("marshal correlations response: %v", err)
}
const want = `{
"correlations":[
{
"avg_delay":"1m0s",
"confidence":0.1,
"description":"When node-1 experiences high_cpu, vm-1 often follows within 1m0s",
"event_pattern":"high_cpu -\u003e restart",
"last_seen":"2026-03-18T17:30:00Z",
"occurrences":1,
"source_id":"node-1",
"source_name":"node-1",
"source_type":"node",
"target_id":"vm-1",
"target_name":"vm-1",
"target_type":"vm"
}
],
"count":1
}`
assertJSONSnapshot(t, got, want)
}
func TestContract_MonitoredSystemLedgerJSONSnapshot(t *testing.T) {
payload := MonitoredSystemLedgerResponse{
Systems: []MonitoredSystemLedgerEntry{
{
Name: "Tower",
Type: "host",
Status: "warning",
StatusExplanation: MonitoredSystemLedgerStatusExplanation{
Summary: "At least one included source is stale, so Pulse marks this monitored system as warning.",
Reasons: []MonitoredSystemLedgerStatusReason{
{
Kind: "source-stale",
Name: "Tower",
Type: "host",
Source: "agent",
Status: "stale",
ReportedAt: "2026-03-18T17:25:00Z",
Summary: "Agent data for Tower is stale (last reported 2026-03-18T17:25:00Z).",
},
},
},
LatestIncludedSignal: MonitoredSystemLedgerLatestSignal{
Name: "Tower",
Type: "host",
Source: "agent",
At: "2026-03-18T17:30:00Z",
},
Source: "agent",
Explanation: MonitoredSystemLedgerExplanation{
Summary: "Counts as one monitored system because Pulse sees one top-level host view from agent.",
Reasons: []MonitoredSystemLedgerExplanationReason{
{
Kind: "standalone",
Signal: "single-top-level-view",
Summary: "No overlapping top-level source matched this system.",
},
},
Surfaces: []MonitoredSystemLedgerExplanationSurface{
{
Name: "Tower",
Type: "host",
Source: "agent",
},
},
},
},
},
Total: 1,
}
got, err := json.Marshal(payload)
if err != nil {
t.Fatalf("marshal monitored system ledger response: %v", err)
}
const want = `{
"systems":[
{
"name":"Tower",
"type":"host",
"status":"warning",
"status_explanation":{
"summary":"At least one included source is stale, so Pulse marks this monitored system as warning.",
"reasons":[
{
"kind":"source-stale",
"name":"Tower",
"type":"host",
"source":"agent",
"status":"stale",
"reported_at":"2026-03-18T17:25:00Z",
"summary":"Agent data for Tower is stale (last reported 2026-03-18T17:25:00Z)."
}
]
},
"latest_included_signal":{
"name":"Tower",
"type":"host",
"source":"agent",
"at":"2026-03-18T17:30:00Z"
},
"source":"agent",
"explanation":{
"summary":"Counts as one monitored system because Pulse sees one top-level host view from agent.",
"reasons":[
{
"kind":"standalone",
"signal":"single-top-level-view",
"summary":"No overlapping top-level source matched this system."
}
],
"surfaces":[
{
"name":"Tower",
"type":"host",
"source":"agent"
}
]
}
}
],
"total":1
}`
assertJSONSnapshot(t, got, want)
}
func TestContract_MonitoredSystemLedgerPreviewJSONSnapshot(t *testing.T) {
payload := MonitoredSystemLedgerPreviewResponse{
CurrentCount: 1,
ProjectedCount: 1,
AdditionalCount: 0,
Effect: "attaches_existing",
CurrentSystems: []MonitoredSystemLedgerEntry{
{
Name: "Tower",
Type: "host",
Status: "online",
StatusExplanation: MonitoredSystemLedgerStatusExplanation{
Summary: "All included top-level collection paths currently report online status.",
Reasons: []MonitoredSystemLedgerStatusReason{},
},
LatestIncludedSignal: MonitoredSystemLedgerLatestSignal{
Name: "Tower",
Type: "host",
Source: "agent",
At: "2026-03-18T17:30:00Z",
},
Source: "agent",
Explanation: MonitoredSystemLedgerExplanation{
Summary: "Counts as one monitored system because Pulse sees one top-level host view from agent.",
Reasons: []MonitoredSystemLedgerExplanationReason{
{
Kind: "standalone",
Signal: "single-top-level-view",
Summary: "No overlapping top-level source matched this system.",
},
},
Surfaces: []MonitoredSystemLedgerExplanationSurface{
{Name: "Tower", Type: "host", Source: "agent"},
},
},
},
},
ProjectedSystems: []MonitoredSystemLedgerEntry{
{
Name: "tower",
Type: "proxmox-node",
Status: "online",
StatusExplanation: MonitoredSystemLedgerStatusExplanation{
Summary: "All included top-level collection paths currently report online status.",
Reasons: []MonitoredSystemLedgerStatusReason{},
},
LatestIncludedSignal: MonitoredSystemLedgerLatestSignal{
Name: "tower",
Type: "proxmox-node",
Source: "proxmox",
At: "2026-03-18T17:35:00Z",
},
Source: "multiple",
Explanation: MonitoredSystemLedgerExplanation{
Summary: "Counts as one monitored system because Pulse merged 2 top-level views into one canonical system using shared machine identity.",
Reasons: []MonitoredSystemLedgerExplanationReason{
{
Kind: "grouped",
Signal: "identity-match",
Summary: "Pulse matched these top-level views by shared canonical machine identity.",
},
},
Surfaces: []MonitoredSystemLedgerExplanationSurface{
{Name: "Tower", Type: "host", Source: "agent"},
{Name: "tower", Type: "proxmox-node", Source: "proxmox"},
},
},
},
},
CurrentSystem: &MonitoredSystemLedgerEntry{
Name: "Tower",
Type: "host",
Status: "online",
StatusExplanation: MonitoredSystemLedgerStatusExplanation{
Summary: "All included top-level collection paths currently report online status.",
Reasons: []MonitoredSystemLedgerStatusReason{},
},
LatestIncludedSignal: MonitoredSystemLedgerLatestSignal{
Name: "Tower",
Type: "host",
Source: "agent",
At: "2026-03-18T17:30:00Z",
},
Source: "agent",
Explanation: MonitoredSystemLedgerExplanation{
Summary: "Counts as one monitored system because Pulse sees one top-level host view from agent.",
Reasons: []MonitoredSystemLedgerExplanationReason{
{
Kind: "standalone",
Signal: "single-top-level-view",
Summary: "No overlapping top-level source matched this system.",
},
},
Surfaces: []MonitoredSystemLedgerExplanationSurface{
{Name: "Tower", Type: "host", Source: "agent"},
},
},
},
ProjectedSystem: &MonitoredSystemLedgerEntry{
Name: "tower",
Type: "proxmox-node",
Status: "online",
StatusExplanation: MonitoredSystemLedgerStatusExplanation{
Summary: "All included top-level collection paths currently report online status.",
Reasons: []MonitoredSystemLedgerStatusReason{},
},
LatestIncludedSignal: MonitoredSystemLedgerLatestSignal{
Name: "tower",
Type: "proxmox-node",
Source: "proxmox",
At: "2026-03-18T17:35:00Z",
},
Source: "multiple",
Explanation: MonitoredSystemLedgerExplanation{
Summary: "Counts as one monitored system because Pulse merged 2 top-level views into one canonical system using shared machine identity.",
Reasons: []MonitoredSystemLedgerExplanationReason{
{
Kind: "grouped",
Signal: "identity-match",
Summary: "Pulse matched these top-level views by shared canonical machine identity.",
},
},
Surfaces: []MonitoredSystemLedgerExplanationSurface{
{Name: "Tower", Type: "host", Source: "agent"},
{Name: "tower", Type: "proxmox-node", Source: "proxmox"},
},
},
},
}
got, err := json.Marshal(payload.NormalizeCollections())
if err != nil {
t.Fatalf("marshal monitored system ledger preview response: %v", err)
}
const want = `{
"current_count":1,
"projected_count":1,
"additional_count":0,
"effect":"attaches_existing",
"current_systems":[
{
"name":"Tower",
"type":"host",
"status":"online",
"status_explanation":{
"summary":"All included top-level collection paths currently report online status.",
"reasons":[]
},
"latest_included_signal":{
"name":"Tower",
"type":"host",
"source":"agent",
"at":"2026-03-18T17:30:00Z"
},
"source":"agent",
"explanation":{
"summary":"Counts as one monitored system because Pulse sees one top-level host view from agent.",
"reasons":[
{
"kind":"standalone",
"signal":"single-top-level-view",
"summary":"No overlapping top-level source matched this system."
}
],
"surfaces":[
{
"name":"Tower",
"type":"host",
"source":"agent"
}
]
}
}
],
"projected_systems":[
{
"name":"tower",
"type":"proxmox-node",
"status":"online",
"status_explanation":{
"summary":"All included top-level collection paths currently report online status.",
"reasons":[]
},
"latest_included_signal":{
"name":"tower",
"type":"proxmox-node",
"source":"proxmox",
"at":"2026-03-18T17:35:00Z"
},
"source":"multiple",
"explanation":{
"summary":"Counts as one monitored system because Pulse merged 2 top-level views into one canonical system using shared machine identity.",
"reasons":[
{
"kind":"grouped",
"signal":"identity-match",
"summary":"Pulse matched these top-level views by shared canonical machine identity."
}
],
"surfaces":[
{
"name":"Tower",
"type":"host",
"source":"agent"
},
{
"name":"tower",
"type":"proxmox-node",
"source":"proxmox"
}
]
}
}
],
"current_system":{
"name":"Tower",
"type":"host",
"status":"online",
"status_explanation":{
"summary":"All included top-level collection paths currently report online status.",
"reasons":[]
},
"latest_included_signal":{
"name":"Tower",
"type":"host",
"source":"agent",
"at":"2026-03-18T17:30:00Z"
},
"source":"agent",
"explanation":{
"summary":"Counts as one monitored system because Pulse sees one top-level host view from agent.",
"reasons":[
{
"kind":"standalone",
"signal":"single-top-level-view",
"summary":"No overlapping top-level source matched this system."
}
],
"surfaces":[
{
"name":"Tower",
"type":"host",
"source":"agent"
}
]
}
},
"projected_system":{
"name":"tower",
"type":"proxmox-node",
"status":"online",
"status_explanation":{
"summary":"All included top-level collection paths currently report online status.",
"reasons":[]
},
"latest_included_signal":{
"name":"tower",
"type":"proxmox-node",
"source":"proxmox",
"at":"2026-03-18T17:35:00Z"
},
"source":"multiple",
"explanation":{
"summary":"Counts as one monitored system because Pulse merged 2 top-level views into one canonical system using shared machine identity.",
"reasons":[
{
"kind":"grouped",
"signal":"identity-match",
"summary":"Pulse matched these top-level views by shared canonical machine identity."
}
],
"surfaces":[
{
"name":"Tower",
"type":"host",
"source":"agent"
},
{
"name":"tower",
"type":"proxmox-node",
"source":"proxmox"
}
]
}
}
}`
assertJSONSnapshot(t, got, want)
}
func TestContract_MonitoredSystemLedgerDoesNotEmitCompatibilityAliases(t *testing.T) {
entry := monitoredSystemLedgerEntry(unifiedresources.MonitoredSystemRecord{
Name: "Tower",
Type: "host",
Status: unifiedresources.StatusWarning,
StatusExplanation: unifiedresources.MonitoredSystemStatusExplanation{
Summary: "At least one included source is stale, so Pulse marks this monitored system as warning.",
Reasons: []unifiedresources.MonitoredSystemStatusReason{},
},
LastSeen: time.Date(2026, 3, 18, 17, 35, 0, 0, time.UTC),
LatestIncludedSignal: unifiedresources.MonitoredSystemLatestSignal{
Name: "tower.local",
Type: "docker-host",
Source: "docker",
At: time.Date(2026, 3, 18, 17, 30, 0, 0, time.UTC),
},
Source: "multiple",
Explanation: unifiedresources.MonitoredSystemGroupingExplanation{
Summary: "Counts as one monitored system because Pulse merged 2 top-level views into one canonical system using shared machine identity.",
Reasons: []unifiedresources.MonitoredSystemGroupingReason{},
Surfaces: []unifiedresources.MonitoredSystemGroupingSurface{},
},
})
payload := MonitoredSystemLedgerResponse{
Systems: []MonitoredSystemLedgerEntry{entry},
Total: 1,
}
got, err := json.Marshal(payload)
if err != nil {
t.Fatalf("marshal monitored system ledger response: %v", err)
}
const want = `{
"systems":[
{
"name":"Tower",
"type":"host",
"status":"warning",
"status_explanation":{
"summary":"At least one included source is stale, so Pulse marks this monitored system as warning.",
"reasons":[]
},
"latest_included_signal":{
"name":"tower.local",
"type":"docker-host",
"source":"docker",
"at":"2026-03-18T17:30:00Z"
},
"source":"multiple",
"explanation":{
"summary":"Counts as one monitored system because Pulse merged 2 top-level views into one canonical system using shared machine identity.",
"reasons":[],
"surfaces":[]
}
}
],
"total":1
}`
assertJSONSnapshot(t, got, want)
}
func TestContract_ResolveAuthEnvPathUsesCanonicalRuntimeDataDir(t *testing.T) {
envDir := t.TempDir()
t.Setenv("PULSE_DATA_DIR", envDir)
explicitDir := t.TempDir()
if got := resolveAuthEnvPath(explicitDir); got != filepath.Join(explicitDir, ".env") {
t.Fatalf("resolveAuthEnvPath(explicit) = %q, want %q", got, filepath.Join(explicitDir, ".env"))
}
if got := resolveAuthEnvPath(""); got != filepath.Join(envDir, ".env") {
t.Fatalf("resolveAuthEnvPath(env fallback) = %q, want %q", got, filepath.Join(envDir, ".env"))
}
}
func TestContract_ResolveAuthEnvWritePathsDeduplicatesCanonicalFallback(t *testing.T) {
envDir := t.TempDir()
t.Setenv("PULSE_DATA_DIR", envDir)
paths := resolveAuthEnvWritePaths("", "")
if len(paths) != 1 {
t.Fatalf("resolveAuthEnvWritePaths() len = %d, want 1", len(paths))
}
if want := filepath.Join(envDir, ".env"); paths[0] != want {
t.Fatalf("resolveAuthEnvWritePaths()[0] = %q, want %q", paths[0], want)
}
}
func TestContract_WriteAuthEnvFileFallsBackToDataPath(t *testing.T) {
configPathFile := filepath.Join(t.TempDir(), "blocked")
if err := os.WriteFile(configPathFile, []byte("blocked"), 0600); err != nil {
t.Fatalf("write blocked config path file: %v", err)
}
dataDir := t.TempDir()
writtenPath, err := writeAuthEnvFile(configPathFile, dataDir, []byte("PULSE_AUTH_USER='pulse'\n"))
if err != nil {
t.Fatalf("writeAuthEnvFile() error = %v", err)
}
wantPath := filepath.Join(dataDir, ".env")
if writtenPath != wantPath {
t.Fatalf("writeAuthEnvFile() path = %q, want %q", writtenPath, wantPath)
}
if _, err := os.Stat(wantPath); err != nil {
t.Fatalf("stat fallback auth env: %v", err)
}
}
func TestContract_RecoveryTokenPersistenceJSONSnapshot(t *testing.T) {
payload := []*RecoveryToken{
{
TokenHash: recoveryTokenHash("raw-recovery-token"),
CreatedAt: time.Date(2026, 2, 8, 13, 14, 15, 0, time.UTC),
ExpiresAt: time.Date(2026, 2, 8, 14, 14, 15, 0, time.UTC),
Used: true,
UsedAt: time.Date(2026, 2, 8, 13, 24, 15, 0, time.UTC),
IP: "192.168.1.10",
},
}
got, err := json.Marshal(payload)
if err != nil {
t.Fatalf("marshal recovery token persistence: %v", err)
}
const want = `[
{
"token_hash":"59b5880d54ca8c991c09269834d59ea09ab4f467fd4d580a932cd70c5b993fa4",
"created_at":"2026-02-08T13:14:15Z",
"expires_at":"2026-02-08T14:14:15Z",
"used":true,
"used_at":"2026-02-08T13:24:15Z",
"ip":"192.168.1.10"
}
]`
assertJSONSnapshot(t, got, want)
}
func TestContract_PersistentAuthStoresRequireExplicitInitialization(t *testing.T) {
resetPersistentAuthStoresForTests()
t.Cleanup(resetPersistentAuthStoresForTests)
assertPanics := func(name string, fn func()) {
t.Helper()
defer func() {
if recover() == nil {
t.Fatalf("%s should require explicit initialization", name)
}
}()
fn()
}
assertPanics("session store", func() { _ = GetSessionStore() })
assertPanics("csrf store", func() { _ = GetCSRFStore() })
assertPanics("recovery token store", func() { _ = GetRecoveryTokenStore() })
}
func TestContract_HostedSessionAuthPrecedesAnonymousFallback(t *testing.T) {
resetPersistentAuthStoresForTests()
t.Cleanup(resetPersistentAuthStoresForTests)
InitSessionStore(t.TempDir())
store := GetSessionStore()
sessionToken := generateSessionToken()
store.CreateSession(sessionToken, 24*time.Hour, "contract-test", "127.0.0.1", "hosted-owner@example.com")
record, err := config.NewAPITokenRecord("hosted-contract-token.12345678", "hosted-contract", []string{config.ScopeSettingsWrite})
if err != nil {
t.Fatalf("NewAPITokenRecord: %v", err)
}
cfg := &config.Config{
APITokens: []config.APITokenRecord{*record},
}
req := httptest.NewRequest(http.MethodGet, "/api/security/tokens/relay-mobile", nil)
req.AddCookie(&http.Cookie{
Name: "pulse_session",
Value: sessionToken,
})
rec := httptest.NewRecorder()
if !CheckAuth(cfg, rec, req) {
t.Fatal("CheckAuth() = false, want true for valid hosted browser session")
}
if got := rec.Header().Get("X-Authenticated-User"); got != "hosted-owner@example.com" {
t.Fatalf("X-Authenticated-User = %q, want hosted-owner@example.com", got)
}
if got := rec.Header().Get("X-Auth-Method"); got != "session" {
t.Fatalf("X-Auth-Method = %q, want session", got)
}
}
func TestContract_SecureHostedSessionRequiresHostPrefixedCookie(t *testing.T) {
resetPersistentAuthStoresForTests()
t.Cleanup(resetPersistentAuthStoresForTests)
InitSessionStore(t.TempDir())
store := GetSessionStore()
sessionToken := generateSessionToken()
store.CreateSession(sessionToken, 24*time.Hour, "contract-test", "127.0.0.1", "hosted-owner@example.com")
record, err := config.NewAPITokenRecord("hosted-secure-session-token.12345678", "hosted-secure-session", []string{config.ScopeSettingsWrite})
if err != nil {
t.Fatalf("NewAPITokenRecord: %v", err)
}
cfg := &config.Config{
APITokens: []config.APITokenRecord{*record},
}
t.Run("legacy cookie is rejected on secure requests", func(t *testing.T) {
req := httptest.NewRequest(http.MethodGet, "https://pulse.example.test/api/security/tokens/relay-mobile", nil)
req.TLS = &tls.ConnectionState{}
req.AddCookie(&http.Cookie{
Name: cookieNameSession,
Value: sessionToken,
})
rec := httptest.NewRecorder()
if CheckAuth(cfg, rec, req) {
t.Fatal("CheckAuth() = true, want false for legacy session cookie on secure request")
}
if rec.Code != http.StatusUnauthorized {
t.Fatalf("status = %d, want %d when secure request only presents legacy cookie", rec.Code, http.StatusUnauthorized)
}
})
t.Run("host-prefixed cookie remains valid on secure requests", func(t *testing.T) {
req := httptest.NewRequest(http.MethodGet, "https://pulse.example.test/api/security/tokens/relay-mobile", nil)
req.TLS = &tls.ConnectionState{}
req.AddCookie(&http.Cookie{
Name: cookieNameSessionSecure,
Value: sessionToken,
})
rec := httptest.NewRecorder()
if !CheckAuth(cfg, rec, req) {
t.Fatal("CheckAuth() = false, want true for __Host- session cookie on secure request")
}
if got := rec.Header().Get("X-Authenticated-User"); got != "hosted-owner@example.com" {
t.Fatalf("X-Authenticated-User = %q, want hosted-owner@example.com", got)
}
})
}
func TestContract_UniversalRateLimitStateIsScopedPerRouterConfig(t *testing.T) {
handler := http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
w.WriteHeader(http.StatusOK)
})
first := UniversalRateLimitMiddlewareWithConfig(newEndpointRateLimitConfig(), handler)
second := UniversalRateLimitMiddlewareWithConfig(newEndpointRateLimitConfig(), handler)
makeRequest := func(target http.Handler) *httptest.ResponseRecorder {
t.Helper()
req := httptest.NewRequest(http.MethodPost, "/api/auth/login", nil)
req.RemoteAddr = "198.51.100.25:12345"
rec := httptest.NewRecorder()
target.ServeHTTP(rec, req)
return rec
}
for i := 0; i < 10; i++ {
if rec := makeRequest(first); rec.Code != http.StatusOK {
t.Fatalf("first router request %d status = %d, want %d", i+1, rec.Code, http.StatusOK)
}
}
if rec := makeRequest(first); rec.Code != http.StatusTooManyRequests {
t.Fatalf("first router overflow status = %d, want %d", rec.Code, http.StatusTooManyRequests)
}
if rec := makeRequest(second); rec.Code != http.StatusOK {
t.Fatalf("second router first request status = %d, want %d", rec.Code, http.StatusOK)
}
}
func TestContract_RouterShutdownClosesOwnedPersistentAuthStoresAfterGlobalRebind(t *testing.T) {
resetPersistentAuthStoresForTests()
t.Cleanup(resetPersistentAuthStoresForTests)
routerOne := NewRouter(&config.Config{DataPath: t.TempDir()}, nil, nil, nil, nil, "1.0.0")
routerTwo := NewRouter(&config.Config{DataPath: t.TempDir()}, nil, nil, nil, nil, "1.0.0")
t.Cleanup(routerTwo.shutdownBackgroundWorkers)
if routerOne.sessionStore == nil || routerOne.csrfStore == nil {
t.Fatal("routerOne should capture initialized persistent auth stores")
}
if routerOne.recoveryTokenStore == nil {
t.Fatal("routerOne should capture initialized recovery token store")
}
if routerTwo.sessionStore == nil || routerTwo.csrfStore == nil {
t.Fatal("routerTwo should capture initialized persistent auth stores")
}
if routerTwo.recoveryTokenStore == nil {
t.Fatal("routerTwo should capture initialized recovery token store")
}
if routerOne.sessionStore == routerTwo.sessionStore {
t.Fatal("router instances should not share the same session store after rebind")
}
if routerOne.csrfStore == routerTwo.csrfStore {
t.Fatal("router instances should not share the same csrf store after rebind")
}
if routerOne.recoveryTokenStore == routerTwo.recoveryTokenStore {
t.Fatal("router instances should not share the same recovery token store after rebind")
}
routerOne.shutdownBackgroundWorkers()
select {
case <-routerOne.sessionStore.workerDone:
default:
t.Fatal("routerOne session store worker should be closed after router shutdown")
}
select {
case <-routerOne.csrfStore.workerDone:
default:
t.Fatal("routerOne csrf store worker should be closed after router shutdown")
}
select {
case <-routerTwo.sessionStore.workerDone:
t.Fatal("routerTwo session store should remain active when routerOne shuts down")
default:
}
select {
case <-routerTwo.csrfStore.workerDone:
t.Fatal("routerTwo csrf store should remain active when routerOne shuts down")
default:
}
select {
case <-routerOne.recoveryTokenStore.stopCleanup:
default:
t.Fatal("routerOne recovery token store should be closed after router shutdown")
}
select {
case <-routerTwo.recoveryTokenStore.stopCleanup:
t.Fatal("routerTwo recovery token store should remain active when routerOne shuts down")
default:
}
}
func TestContract_HostedOrgManagerSessionCanMintRelayMobileToken(t *testing.T) {
defer SetMultiTenantEnabled(false)
SetMultiTenantEnabled(true)
t.Setenv("PULSE_DEV", "true")
dataDir := t.TempDir()
hashed, err := authpkg.HashPassword("Password!1")
if err != nil {
t.Fatalf("hash password: %v", err)
}
cfg := &config.Config{
DataPath: dataDir,
ConfigPath: dataDir,
AuthUser: "platform-admin",
AuthPass: hashed,
}
mtp := config.NewMultiTenantPersistence(dataDir)
org := &models.Organization{
ID: "org-a",
DisplayName: "Org A",
OwnerUserID: "operator-owner",
Members: []models.OrganizationMember{
{UserID: "legacy-owner", Role: models.OrgRoleOwner, AddedAt: time.Now()},
{UserID: "operator-owner", Role: models.OrgRoleOwner, AddedAt: time.Now()},
},
}
if err := mtp.SaveOrganization(org); err != nil {
t.Fatalf("save organization: %v", err)
}
router := newMultiTenantRouter(t, cfg)
setLicenseTierForHandlersForTests(t, router.licenseHandlers, "org-a", pkglicensing.TierRelay)
sessionToken := "relay-owner-session-" + strings.ReplaceAll(time.Now().UTC().Format(time.RFC3339Nano), ":", "-")
GetSessionStore().CreateSession(sessionToken, time.Hour, "agent", "127.0.0.1", "legacy-owner")
req := httptest.NewRequest(http.MethodPost, "/api/security/tokens/relay-mobile", strings.NewReader(`{}`))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("X-CSRF-Token", generateCSRFToken(sessionToken))
req.Header.Set("X-Pulse-Org-ID", "org-a")
req.AddCookie(&http.Cookie{Name: cookieNameSession, Value: sessionToken})
rec := httptest.NewRecorder()
router.Handler().ServeHTTP(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("relay mobile token route status = %d, want %d: %s", rec.Code, http.StatusOK, rec.Body.String())
}
var payload struct {
Token string `json:"token"`
Record apiTokenDTO `json:"record"`
}
if err := json.NewDecoder(rec.Body).Decode(&payload); err != nil {
t.Fatalf("decode relay mobile response: %v", err)
}
if payload.Token == "" {
t.Fatal("expected relay mobile token in response")
}
if payload.Record.OwnerUserID != "legacy-owner" {
t.Fatalf("ownerUserId = %q, want legacy-owner", payload.Record.OwnerUserID)
}
if len(cfg.APITokens) != 1 {
t.Fatalf("expected one stored token, got %d", len(cfg.APITokens))
}
if cfg.APITokens[0].OrgID != "org-a" {
t.Fatalf("stored token orgId = %q, want org-a", cfg.APITokens[0].OrgID)
}
}
func TestContract_RelayMobileScopeCanReadOnboardingDeepLink(t *testing.T) {
rawToken := "relay-mobile-onboarding-token-123.12345678"
record := newTokenRecord(t, rawToken, []string{config.ScopeRelayMobileAccess}, nil)
cfg := newTestConfigWithTokens(t, record)
router := NewRouter(cfg, nil, nil, nil, nil, "1.0.0")
req := httptest.NewRequest(http.MethodGet, "/api/onboarding/deep-link", nil)
req.Header.Set("X-API-Token", rawToken)
rec := httptest.NewRecorder()
router.Handler().ServeHTTP(rec, req)
if rec.Code == http.StatusForbidden && strings.Contains(rec.Body.String(), "missing_scope") {
t.Fatalf("relay mobile scope should satisfy onboarding deep-link gating, got %d: %s", rec.Code, rec.Body.String())
}
}
func TestContract_OnboardingDeepLinkIgnoresQueryAuthToken(t *testing.T) {
rawToken := "relay-mobile-onboarding-query-token.12345678"
record := newTokenRecord(t, rawToken, []string{config.ScopeRelayMobileAccess}, nil)
cfg := newTestConfigWithTokens(t, record)
router := NewRouter(cfg, nil, nil, nil, nil, "1.0.0")
req := httptest.NewRequest(http.MethodGet, "/api/onboarding/deep-link?auth_token="+url.QueryEscape(rawToken), nil)
rec := httptest.NewRecorder()
router.Handler().ServeHTTP(rec, req)
if rec.Code != http.StatusUnauthorized {
t.Fatalf("status = %d, want %d when auth token is only provided in query string", rec.Code, http.StatusUnauthorized)
}
}
func TestContract_SetupTokenRoutesRejectQueryAuthToken(t *testing.T) {
cfg := newTestConfigWithTokens(t, newTokenRecord(t, "settings-write-token", []string{config.ScopeSettingsWrite}, nil))
router := NewRouter(cfg, nil, nil, nil, nil, "1.0.0")
t.Setenv("HOME", t.TempDir())
token := "fedcba9876543210fedcba9876543210"
tokenHash := authpkg.HashAPIToken(token)
router.configHandlers.codeMutex.Lock()
router.configHandlers.setupTokens[tokenHash] = &SetupTokenRecord{ExpiresAt: time.Now().Add(time.Minute)}
router.configHandlers.codeMutex.Unlock()
t.Run("verify-temperature-ssh requires header token transport", func(t *testing.T) {
req := httptest.NewRequest(http.MethodPost, "/api/system/verify-temperature-ssh?auth_token="+token, strings.NewReader(`{"nodes":""}`))
rec := httptest.NewRecorder()
router.Handler().ServeHTTP(rec, req)
if rec.Code != http.StatusUnauthorized {
t.Fatalf("status = %d, want %d when setup token is only in query string", rec.Code, http.StatusUnauthorized)
}
req = httptest.NewRequest(http.MethodPost, "/api/system/verify-temperature-ssh", strings.NewReader(`{"nodes":""}`))
req.Header.Set("X-Setup-Token", token)
rec = httptest.NewRecorder()
router.Handler().ServeHTTP(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("status = %d, want %d when setup token is provided in header", rec.Code, http.StatusOK)
}
})
t.Run("ssh-config requires header token transport", func(t *testing.T) {
req := httptest.NewRequest(http.MethodPost, "/api/system/ssh-config?auth_token="+token, strings.NewReader("Host example\nHostname example\n"))
rec := httptest.NewRecorder()
router.Handler().ServeHTTP(rec, req)
if rec.Code != http.StatusUnauthorized {
t.Fatalf("status = %d, want %d when setup token is only in query string", rec.Code, http.StatusUnauthorized)
}
req = httptest.NewRequest(http.MethodPost, "/api/system/ssh-config", strings.NewReader("Host example\nHostname example\n"))
req.Header.Set("X-Setup-Token", token)
rec = httptest.NewRecorder()
router.Handler().ServeHTTP(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("status = %d, want %d when setup token is provided in header", rec.Code, http.StatusOK)
}
})
}
func TestContract_RelayMobileScopeCannotReadApprovalDetail(t *testing.T) {
rawToken := "relay-mobile-approval-detail-token-123.12345678"
record := newTokenRecord(t, rawToken, []string{config.ScopeRelayMobileAccess}, nil)
cfg := newTestConfigWithTokens(t, record)
router := NewRouter(cfg, nil, nil, nil, nil, "1.0.0")
req := httptest.NewRequest(http.MethodGet, "/api/ai/approvals/approval-1", nil)
req.Header.Set("X-API-Token", rawToken)
rec := httptest.NewRecorder()
router.Handler().ServeHTTP(rec, req)
if rec.Code != http.StatusForbidden {
t.Fatalf("relay mobile scope should not satisfy approval detail gating, got %d: %s", rec.Code, rec.Body.String())
}
if !strings.Contains(rec.Body.String(), config.ScopeAIExecute) {
t.Fatalf("relay mobile approval detail rejection should mention %q, got %s", config.ScopeAIExecute, rec.Body.String())
}
}
func TestContract_UnifiedAgentReportResponseJSONSnapshot(t *testing.T) {
payload := map[string]any{
"success": true,
"agentId": "agent-123",
"lastSeen": "2026-02-08T13:14:15Z",
"platform": "linux",
"osName": "Debian GNU/Linux",
"osVersion": "12",
"config": map[string]any{
"commandsEnabled": true,
},
}
got, err := json.Marshal(payload)
if err != nil {
t.Fatalf("marshal unified agent report response: %v", err)
}
const want = `{
"agentId":"agent-123",
"config":{"commandsEnabled":true},
"lastSeen":"2026-02-08T13:14:15Z",
"osName":"Debian GNU/Linux",
"osVersion":"12",
"platform":"linux",
"success":true
}`
assertJSONSnapshot(t, got, want)
}
func TestContract_UnifiedAgentLookupFailsClosedOnAmbiguousHostname(t *testing.T) {
handler := newUnifiedAgentHandlerForTests(t,
models.Host{ID: "host-1", Hostname: "webserver.corp.example.com", DisplayName: "Web One"},
models.Host{ID: "host-2", Hostname: "webserver.other.example.com", DisplayName: "Web Two"},
)
req := httptest.NewRequest(http.MethodGet, "/api/agents/agent/lookup?hostname=webserver", nil)
rec := httptest.NewRecorder()
handler.HandleLookup(rec, req)
if rec.Code != http.StatusNotFound {
t.Fatalf("expected not found status for ambiguous hostname lookup, got %d: %s", rec.Code, rec.Body.String())
}
var resp struct {
Error string `json:"error"`
Code string `json:"code"`
}
if err := json.NewDecoder(rec.Body).Decode(&resp); err != nil {
t.Fatalf("decode ambiguous lookup error response: %v", err)
}
if resp.Code != "agent_not_found" {
t.Fatalf("expected agent_not_found code, got %q", resp.Code)
}
}
func TestContract_HostsShareResolvedIdentityTreatsLoopbackAliasAsSameNode(t *testing.T) {
if !hostsShareResolvedIdentity("https://localhost:7655", "https://127.0.0.1:7655") {
t.Fatal("expected localhost and loopback IP to resolve as the same host identity")
}
if hostsShareResolvedIdentity("https://192.0.2.10:7655", "https://192.0.2.11:7655") {
t.Fatal("expected different IP endpoints to remain distinct host identities")
}
}
func TestContract_DiagnosticsDockerPrepareTokenInstallCommandUsesLifecycleTransport(t *testing.T) {
baseURL := "https://pulse.example.com/base"
got := buildContainerRuntimeAgentInstallCommand(baseURL, "token-123")
if !strings.Contains(got, posixShellQuote(baseURL+"/install.sh")) {
t.Fatalf("install command missing normalized install script URL: %s", got)
}
if !strings.Contains(got, "--enable-host=false") {
t.Fatalf("install command missing canonical host-disable flag: %s", got)
}
if strings.Contains(got, "--disable-host") {
t.Fatalf("install command preserved stale disable-host flag: %s", got)
}
if !strings.Contains(got, `| { if [ "$(id -u)" -eq 0 ]; then bash -s --`) {
t.Fatalf("install command missing governed root-or-sudo wrapper: %s", got)
}
if strings.Contains(got, "curl -fsSL "+posixShellQuote(baseURL+"/install.sh")+" | sudo bash -s --") {
t.Fatalf("install command preserved raw sudo pipe instead of governed wrapper: %s", got)
}
}
func TestContract_DiagnosticsDockerPrepareTokenOptionalAuthInstallCommandOmitsToken(t *testing.T) {
got := buildContainerRuntimeAgentInstallCommand("http://pulse.example.com:7655/", "")
if strings.Contains(got, "--token") {
t.Fatalf("optional-auth install command preserved token flag: %s", got)
}
if !strings.Contains(got, "--insecure") {
t.Fatalf("optional-auth install command missing insecure flag for plain HTTP Pulse URL: %s", got)
}
}
func TestContract_SetupScriptURLCommandUsesFailFastQuotedTransport(t *testing.T) {
url := "https://pulse.example.com/api/setup-script?type=pve&host=pve1.local"
got := buildSetupScriptCommand(url, "token-123")
if !strings.Contains(got, "curl -fsSL "+posixShellQuote(url)+" | ") {
t.Fatalf("setup-script command missing canonical fail-fast transport: %s", got)
}
if !strings.Contains(got, `if [ "$(id -u)" -eq 0 ]; then PULSE_SETUP_TOKEN=`+posixShellQuote("token-123")+` bash`) {
t.Fatalf("setup-script command missing direct-root execution path: %s", got)
}
if !strings.Contains(got, `elif command -v sudo >/dev/null 2>&1; then sudo env PULSE_SETUP_TOKEN=`+posixShellQuote("token-123")+` bash`) {
t.Fatalf("setup-script command missing sudo execution path: %s", got)
}
if strings.Contains(got, "curl -sSL ") {
t.Fatalf("setup-script command preserved stale non-fail-fast curl transport: %s", got)
}
}
func TestContract_SetupScriptEmbedsFailFastGuidance(t *testing.T) {
tempDir := t.TempDir()
cfg := &config.Config{
DataPath: tempDir,
ConfigPath: tempDir,
}
handlers := newTestConfigHandlers(t, cfg)
req := httptest.NewRequest(http.MethodGet,
"/api/setup-script?type=pve&host=http://sentinel-host:8006&pulse_url=http://sentinel-url:7656", nil)
rec := httptest.NewRecorder()
handlers.HandleSetupScript(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("status = %d, want %d", rec.Code, http.StatusOK)
}
script := rec.Body.String()
if !strings.Contains(script, `PULSE_BOOTSTRAP_COMMAND_WITH_ENV='curl -fsSL '"'"'http://sentinel-url:7656/api/setup-script?host=http%3A%2F%2Fsentinel-host%3A8006&pulse_url=http%3A%2F%2Fsentinel-url%3A7656&type=pve'"'"' | `) {
t.Fatalf("setup script missing canonical bootstrap command owner: %s", script)
}
if strings.Contains(script, `PULSE_BOOTSTRAP_COMMAND_WITH_ENV='curl -fsSL '"'"'http://sentinel-url:7656/api/setup-script?host=http%3A%2F%2Fsentinel-host%3A8006&pulse_url=http%3A%2F%2Fsentinel-url%3A7656&type=pve'"'"' | { if [ "$(id -u)" -eq 0 ]; then PULSE_SETUP_TOKEN=`) {
t.Fatalf("setup script bootstrap command should defer setup token to runtime hydration, got: %s", script)
}
if !strings.Contains(script, `echo " $PULSE_BOOTSTRAP_COMMAND_WITH_ENV"`) {
t.Fatalf("setup script missing bootstrap-command retry guidance: %s", script)
}
if !strings.Contains(script, `SETUP_SCRIPT_URL="http://sentinel-url:7656/api/setup-script?host=http%3A%2F%2Fsentinel-host%3A8006&pulse_url=http%3A%2F%2Fsentinel-url%3A7656&type=pve"`) {
t.Fatalf("setup script missing canonical encoded retry URL: %s", script)
}
if !strings.Contains(script, `PULSE_SETUP_TOKEN="${PULSE_SETUP_TOKEN:-`) {
t.Fatalf("setup script missing canonical PVE setup-token initialization before rerun guidance: %s", script)
}
if !strings.Contains(script, `echo "Root privileges required. Run as root (su -) and retry."`) {
t.Fatalf("setup script missing canonical root requirement guidance: %s", script)
}
if !strings.Contains(script, `echo "This setup flow must run on the Proxmox host so Pulse can create"`) {
t.Fatalf("setup script missing canonical off-host rerun guidance: %s", script)
}
if strings.Contains(script, `echo " curl -sSL \"$SETUP_SCRIPT_URL\" | bash"`) || strings.Contains(script, `echo " curl -sSL \"$PULSE_URL/api/setup-script?type=pve&host=YOUR_PVE_URL&pulse_url=$PULSE_URL\" | bash"`) {
t.Fatalf("setup script preserved stale non-fail-fast guidance: %s", script)
}
if strings.Contains(script, `echo "Manual setup steps:"`) || strings.Contains(script, `echo " 2. In Pulse: Settings → Nodes → Add Node (enter token from above)"`) {
t.Fatalf("setup script preserved stale off-host manual token flow: %s", script)
}
if !strings.Contains(script, `done <<< "$OLD_TOKENS_PVE"`) {
t.Fatalf("setup script missing explicit pve old-token cleanup loop: %s", script)
}
if !strings.Contains(script, `done <<< "$OLD_TOKENS_PAM"`) {
t.Fatalf("setup script missing explicit pam old-token cleanup loop: %s", script)
}
if strings.Contains(script, `done <<< "$OLD_TOKENS"`) {
t.Fatalf("setup script preserved stale undefined old-token cleanup variable: %s", script)
}
if !strings.Contains(script, `pveum user token remove pulse-monitor@pve "$TOKEN"`) {
t.Fatalf("setup script missing pve token cleanup command: %s", script)
}
if !strings.Contains(script, `pveum user token remove pulse-monitor@pam "$TOKEN"`) {
t.Fatalf("setup script missing pam token cleanup command: %s", script)
}
if !strings.Contains(script, `TOKEN_MATCH_PREFIX="pulse-sentinel-url"`) {
t.Fatalf("setup script missing canonical token-match prefix for cleanup discovery: %s", script)
}
if !strings.Contains(script, `grep -E "^${TOKEN_MATCH_PREFIX}(-[0-9]+)?$"`) {
t.Fatalf("setup script missing canonical cleanup token discovery matcher: %s", script)
}
if !strings.Contains(script, `pulse_pve_token_exists() {`) ||
!strings.Contains(script, `grep -Fx "$TOKEN_NAME" >/dev/null 2>&1`) ||
!strings.Contains(script, `if pulse_pve_token_exists; then`) {
t.Fatalf("setup script missing exact PVE token rotation detection: %s", script)
}
if strings.Contains(script, `PULSE_IP_PATTERN=`) {
t.Fatalf("setup script preserved stale ip-pattern cleanup discovery: %s", script)
}
if strings.Contains(script, `grep -q "$TOKEN_NAME"`) {
t.Fatalf("setup script preserved stale broad PVE token rotation detection: %s", script)
}
if !strings.Contains(script, `resolve_authorized_keys_path() {`) {
t.Fatalf("setup script missing authorized_keys symlink resolver: %s", script)
}
if !strings.Contains(script, `resolved="$(readlink -f "$auth_keys" 2>/dev/null || true)"`) {
t.Fatalf("setup script missing canonical authorized_keys target resolution: %s", script)
}
if !strings.Contains(script, `install_authorized_keys_file() {`) {
t.Fatalf("setup script missing shared authorized_keys install helper: %s", script)
}
if strings.Count(script, `AUTH_KEYS="$(resolve_authorized_keys_path)"`) < 2 {
t.Fatalf("setup script install and uninstall paths must both resolve authorized_keys symlinks: %s", script)
}
if !strings.Contains(script, `grep -vF '# pulse-' "$AUTH_KEYS" > "$TMP_AUTH_KEYS" 2>/dev/null`) {
t.Fatalf("setup script uninstall path must remove all Pulse-managed SSH keys through the resolved authorized_keys target: %s", script)
}
if !strings.Contains(script, `grep -vF "# pulse-" "$AUTH_KEYS" > "$TMP_AUTH_KEYS" 2>/dev/null`) {
t.Fatalf("setup script install path must filter existing Pulse-managed SSH keys through the resolved authorized_keys target: %s", script)
}
if strings.Contains(script, `grep -vF '# pulse-managed-key'`) {
t.Fatalf("setup script preserved stale managed-key-only cleanup: %s", script)
}
if strings.Contains(script, `/root/.ssh/authorized_keys.tmp`) || strings.Contains(script, `echo "$SSH_SENSORS_KEY_ENTRY" >> /root/.ssh/authorized_keys`) {
t.Fatalf("setup script preserved symlink-breaking authorized_keys rewrite: %s", script)
}
if !strings.Contains(script, `PULSE_SENSORS_WRAPPER="/usr/local/sbin/pulse-sensors"`) {
t.Fatalf("setup script missing canonical Pulse sensor wrapper path: %s", script)
}
if !strings.Contains(script, `SSH_SENSORS_KEY_ENTRY="command=\"$PULSE_SENSORS_WRAPPER\",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty $SSH_SENSORS_PUBLIC_KEY # pulse-sensors"`) {
t.Fatalf("setup script must force temperature SSH keys to the Pulse sensor wrapper: %s", script)
}
if !strings.Contains(script, `"smart": collect_smart(),`) || !strings.Contains(script, `apt-get install -y smartmontools`) {
t.Fatalf("setup script missing SMART temperature wrapper contract: %s", script)
}
if strings.Contains(script, `SSH_SENSORS_KEY_ENTRY="command=\"sensors -j\"`) {
t.Fatalf("setup script preserved stale raw sensors forced command: %s", script)
}
if strings.Contains(script, `echo "Please run this script as root"`) {
t.Fatalf("setup script preserved stale root-only guidance: %s", script)
}
if !strings.Contains(script, `grep -Eq '"status"[[:space:]]*:[[:space:]]*"success"'`) {
t.Fatalf("setup script missing secure success detection: %s", script)
}
if strings.Contains(script, `grep -q "success"`) {
t.Fatalf("setup script preserved broad success substring detection: %s", script)
}
if !strings.Contains(script, `curl -fsS -X POST "$PULSE_URL/api/auto-register"`) {
t.Fatalf("setup script missing fail-fast auto-register transport: %s", script)
}
if !strings.Contains(script, `curl -fsS -X POST "$PULSE_URL/api/auto-unregister"`) {
t.Fatalf("setup script missing fail-fast auto-unregister transport: %s", script)
}
if !strings.Contains(script, `"source":"script"`) {
t.Fatalf("setup script missing canonical /api/auto-register source marker: %s", script)
}
if !strings.Contains(script, `echo " • Removing Pulse connection from server..."`) {
t.Fatalf("setup script missing canonical server-side teardown guidance: %s", script)
}
if !strings.Contains(script, `REGISTER_RC=$?`) {
t.Fatalf("setup script missing explicit auto-register curl exit-code handling: %s", script)
}
if !strings.Contains(script, `echo "⚠️ Auto-registration skipped: token value unavailable"`) {
t.Fatalf("setup script missing fail-closed token-value-unavailable guidance: %s", script)
}
if strings.Contains(script, `curl -s -X POST "$PULSE_URL/api/auto-register"`) {
t.Fatalf("setup script preserved stale non-fail-fast auto-register transport: %s", script)
}
if !strings.Contains(script, `echo "The provided Pulse setup token was invalid or expired"`) {
t.Fatalf("setup script missing invalid setup-token guidance: %s", script)
}
if !strings.Contains(script, `echo "Get a fresh setup token from Pulse Settings → Nodes and rerun this script."`) {
t.Fatalf("setup script missing fresh setup-token rerun guidance: %s", script)
}
if !strings.Contains(script, `SETUP_TOKEN_INVALID=true`) {
t.Fatalf("setup script missing PVE auth-failure state tracking: %s", script)
}
if !strings.Contains(script, `echo "Pulse setup token authentication failed."`) {
t.Fatalf("setup script missing PVE auth-failure completion guidance: %s", script)
}
if !strings.Contains(script, `if [ "$AUTO_REG_SUCCESS" != true ] && [ "$SETUP_TOKEN_INVALID" != true ]; then`) {
t.Fatalf("setup script missing PVE auth-failure footer guard: %s", script)
}
if !strings.Contains(script, `echo "📝 Use the token details below in Pulse Settings → Nodes to finish registration."`) {
t.Fatalf("setup script missing canonical auto-register failure continuation guidance: %s", script)
}
if strings.Contains(script, `echo "To enable auto-registration, add your API token to the setup URL"`) {
t.Fatalf("setup script preserved stale API-token auth guidance: %s", script)
}
if strings.Contains(script, `echo "The provided API token was invalid"`) {
t.Fatalf("setup script preserved stale invalid API-token guidance: %s", script)
}
if strings.Contains(script, `echo "To enable auto-registration, rerun with a valid Pulse setup token"`) {
t.Fatalf("setup script preserved stale split setup-token auth guidance: %s", script)
}
if strings.Contains(script, `echo "📝 For manual setup:"`) {
t.Fatalf("setup script preserved stale numbered manual-setup fallback: %s", script)
}
if strings.Contains(script, `echo " 2. Add this node manually in Pulse Settings"`) {
t.Fatalf("setup script preserved stale auto-register failure continuation guidance: %s", script)
}
if !strings.Contains(script, `echo "Pulse monitoring token setup completed."`) {
t.Fatalf("setup script missing truthful manual completion messaging: %s", script)
}
if !strings.Contains(script, `echo "Pulse monitoring token setup failed."`) {
t.Fatalf("setup script missing token-create failure completion messaging: %s", script)
}
if !strings.Contains(script, `echo "Fix the token creation error above and rerun this script on the node."`) {
t.Fatalf("setup script missing immediate token-create failure rerun guidance: %s", script)
}
if !strings.Contains(script, `echo "Resolve the token creation error shown above and rerun this script on the node."`) {
t.Fatalf("setup script missing token-create failure rerun guidance: %s", script)
}
if !strings.Contains(script, `echo " Resolve the token output issue above and rerun this script on the node."`) {
t.Fatalf("setup script missing token-extract failure rerun guidance: %s", script)
}
if !strings.Contains(script, `echo "Successfully registered with Pulse monitoring."`) {
t.Fatalf("setup script missing canonical success messaging: %s", script)
}
if !strings.Contains(script, `echo " Token Value: [See token output above]"`) {
t.Fatalf("setup script missing canonical token placeholder guidance: %s", script)
}
if !strings.Contains(script, `echo "Finish registration in Pulse using the manual setup details below."`) {
t.Fatalf("setup script missing truthful manual registration guidance: %s", script)
}
if !strings.Contains(script, `echo "Add this server to Pulse with:"`) {
t.Fatalf("setup script missing canonical manual-add heading: %s", script)
}
if !strings.Contains(script, `echo "Use these details in Pulse Settings → Nodes to finish registration."`) {
t.Fatalf("setup script missing canonical manual-add continuation guidance: %s", script)
}
if !strings.Contains(script, `echo "⚠️ Auto-registration failed. Finish registration manually in Pulse Settings → Nodes."`) {
t.Fatalf("setup script missing canonical auto-register failure summary: %s", script)
}
if !strings.Contains(script, `echo " Host URL: $SERVER_HOST"`) {
t.Fatalf("setup script missing canonical manual host continuity: %s", script)
}
if strings.Contains(script, `echo "Manual setup instructions:"`) {
t.Fatalf("setup script preserved stale manual setup heading: %s", script)
}
if strings.Contains(script, `echo "Node registered successfully"`) || strings.Contains(script, `echo "Node successfully registered with Pulse monitoring."`) || strings.Contains(script, `echo "✅ Successfully registered with Pulse!"`) || strings.Contains(script, `echo "Server successfully registered with Pulse monitoring."`) {
t.Fatalf("setup script preserved stale success copy variants: %s", script)
}
if strings.Contains(script, `echo " Token Value: [See above]"`) || strings.Contains(script, `echo " Token Value: [Check the output above for the token or instructions]"`) {
t.Fatalf("setup script preserved stale token placeholder guidance: %s", script)
}
if strings.Contains(script, `echo "⚠️ Auto-registration failed. Manual configuration may be needed."`) {
t.Fatalf("setup script preserved stale auto-register failure summary: %s", script)
}
if strings.Contains(script, `PULSE_REG_TOKEN=your-token ./setup.sh`) {
t.Fatalf("setup script preserved stale rerun token guidance: %s", script)
}
if strings.Contains(script, `echo "Manual registration may be required."`) {
t.Fatalf("setup script preserved stale manual-registration token failure guidance: %s", script)
}
if strings.Contains(script, `echo " Host URL: YOUR_PROXMOX_HOST:8006"`) {
t.Fatalf("setup script preserved stale placeholder manual host guidance: %s", script)
}
if !strings.Contains(script, `echo "Pulse monitoring token setup could not be completed."`) {
t.Fatalf("setup script missing token-extract failure completion messaging: %s", script)
}
if !strings.Contains(script, `echo "Resolve the token output issue shown above and rerun this script on the node."`) {
t.Fatalf("setup script missing token-extract completion rerun guidance: %s", script)
}
if !strings.Contains(script, `if [ "$TOKEN_READY" = true ]; then
attempt_auto_registration
else
AUTO_REG_SUCCESS=false
fi`) {
t.Fatalf("setup script does not skip PVE auto-registration when no usable token is ready: %s", script)
}
if !strings.Contains(script, `if [ "$TOKEN_READY" = true ]; then
echo "Add this server to Pulse with:"`) {
t.Fatalf("setup script does not gate PVE manual token details on usable token extraction: %s", script)
}
if strings.Contains(script, `elif [ "$TOKEN_READY" != true ]; then
echo "Pulse monitoring token setup completed."`) {
t.Fatalf("setup script lets PVE token-extract failure fall through to completed token setup: %s", script)
}
pbsReq := httptest.NewRequest(http.MethodGet,
"/api/setup-script?type=pbs&host=https://sentinel-pbs:8007&pulse_url=http://sentinel-url:7656", nil)
pbsRec := httptest.NewRecorder()
handlers.HandleSetupScript(pbsRec, pbsReq)
if pbsRec.Code != http.StatusOK {
t.Fatalf("pbs status = %d, want %d", pbsRec.Code, http.StatusOK)
}
pbsScript := pbsRec.Body.String()
if !strings.Contains(pbsScript, `echo " Host URL: $HOST_URL"`) {
t.Fatalf("setup script missing canonical PBS manual host continuity: %s", pbsScript)
}
if !strings.Contains(pbsScript, `echo "Pulse monitoring token setup failed."`) {
t.Fatalf("setup script missing PBS token-create failure completion messaging: %s", pbsScript)
}
if !strings.Contains(pbsScript, `echo "Pulse monitoring token setup could not be completed."`) {
t.Fatalf("setup script missing PBS token-extract failure completion messaging: %s", pbsScript)
}
if !strings.Contains(pbsScript, `echo "⚠️ Auto-registration skipped: no setup token provided"`) {
t.Fatalf("setup script missing PBS setup-token-skip guidance: %s", pbsScript)
}
if !strings.Contains(pbsScript, `PULSE_SETUP_TOKEN="${PULSE_SETUP_TOKEN:-`) {
t.Fatalf("setup script missing canonical PBS setup-token initialization before rerun guidance: %s", pbsScript)
}
if strings.Contains(pbsScript, `echo "⚠️ Auto-registration skipped: no setup token provided"
AUTO_REG_SUCCESS=false
REGISTER_RESPONSE=""
REGISTER_RC=1`) {
t.Fatalf("setup script still forces fake PBS request-failure state after missing setup-token skip: %s", pbsScript)
}
if strings.Contains(pbsScript, `echo "⚠️ Auto-registration skipped: token value unavailable"
AUTO_REG_SUCCESS=false
REGISTER_RESPONSE=""
REGISTER_RC=1`) {
t.Fatalf("setup script still forces fake PBS request-failure state after token-value skip: %s", pbsScript)
}
if !strings.Contains(pbsScript, `if [ "$REGISTER_ATTEMPTED" != true ]; then`) {
t.Fatalf("setup script does not distinguish skipped PBS auto-registration paths from attempted requests: %s", pbsScript)
}
if !strings.Contains(pbsScript, `echo "The provided Pulse setup token was invalid or expired"`) {
t.Fatalf("setup script missing invalid PBS setup-token guidance: %s", pbsScript)
}
if !strings.Contains(pbsScript, `echo "Get a fresh setup token from Pulse Settings → Nodes and rerun this script."`) {
t.Fatalf("setup script missing fresh PBS setup-token rerun guidance: %s", pbsScript)
}
if !strings.Contains(pbsScript, `SETUP_TOKEN_INVALID=true`) {
t.Fatalf("setup script missing PBS auth-failure state tracking: %s", pbsScript)
}
if !strings.Contains(pbsScript, `echo "Pulse setup token authentication failed."`) {
t.Fatalf("setup script missing PBS auth-failure completion guidance: %s", pbsScript)
}
if !strings.Contains(pbsScript, `if [ "$AUTO_REG_SUCCESS" != true ] && [ "$SETUP_TOKEN_INVALID" != true ]; then`) {
t.Fatalf("setup script missing PBS auth-failure footer guard: %s", pbsScript)
}
if !strings.Contains(pbsScript, `echo "Fix the token creation error above and rerun this script on the node."`) {
t.Fatalf("setup script missing PBS immediate token-create failure rerun guidance: %s", pbsScript)
}
if !strings.Contains(pbsScript, `echo "Resolve the token creation error shown above and rerun this script on the node."`) {
t.Fatalf("setup script missing PBS token-create failure rerun guidance: %s", pbsScript)
}
if !strings.Contains(pbsScript, `echo " Resolve the token output issue above and rerun this script on the node."`) {
t.Fatalf("setup script missing PBS token-extract failure rerun guidance: %s", pbsScript)
}
if !strings.Contains(pbsScript, `echo "Resolve the token output issue shown above and rerun this script on the node."`) {
t.Fatalf("setup script missing PBS token-extract completion rerun guidance: %s", pbsScript)
}
if !strings.Contains(pbsScript, `HOST_URL="https://sentinel-pbs:8007"`) {
t.Fatalf("setup script missing canonical PBS host binding: %s", pbsScript)
}
if !strings.Contains(pbsScript, `TOKEN_MATCH_PREFIX="pulse-sentinel-url"`) {
t.Fatalf("pbs setup script missing canonical token-match prefix for cleanup discovery: %s", pbsScript)
}
if !strings.Contains(pbsScript, `grep -oE "${TOKEN_MATCH_PREFIX}(-[0-9]+)?" | sort -u || true`) {
t.Fatalf("pbs setup script missing canonical cleanup token discovery matcher: %s", pbsScript)
}
if !strings.Contains(pbsScript, `awk '{print $1}' | grep -Fx "$TOKEN_NAME" >/dev/null 2>&1`) {
t.Fatalf("pbs setup script missing exact token rotation detection: %s", pbsScript)
}
tokenCreateIndex := strings.Index(pbsScript, `TOKEN_CREATE_RC=$?`)
bannerIndex := strings.Index(pbsScript, `echo "IMPORTANT: Copy the token value below - it's only shown once!"`)
successBranchIndex := strings.Index(pbsScript, "else\n TOKEN_CREATED=true")
if tokenCreateIndex == -1 || bannerIndex == -1 || successBranchIndex == -1 {
t.Fatalf("pbs setup script missing token-create truth markers: %s", pbsScript)
}
if bannerIndex < tokenCreateIndex {
t.Fatalf("pbs setup script prints token-copy banner before token creation result is known: %s", pbsScript)
}
if bannerIndex < successBranchIndex {
t.Fatalf("pbs setup script prints token-copy banner outside the successful token-create branch: %s", pbsScript)
}
if strings.Contains(pbsScript, `PULSE_IP_PATTERN=`) {
t.Fatalf("pbs setup script preserved stale ip-pattern cleanup discovery: %s", pbsScript)
}
if strings.Contains(pbsScript, `grep -q "$TOKEN_NAME"`) {
t.Fatalf("pbs setup script preserved stale broad token rotation detection: %s", pbsScript)
}
if strings.Index(pbsScript, `HOST_URL="https://sentinel-pbs:8007"`) > strings.Index(pbsScript, `if [ -z "$PULSE_SETUP_TOKEN" ]; then`) {
t.Fatalf("setup script binds PBS host too late for manual fallback continuity: %s", pbsScript)
}
if strings.Index(pbsScript, `HOST_URL="https://sentinel-pbs:8007"`) > strings.Index(pbsScript, `if [ "$TOKEN_CREATE_RC" -ne 0 ]; then`) {
t.Fatalf("setup script binds PBS host too late for token-create failure continuity: %s", pbsScript)
}
if !strings.Contains(pbsScript, `if [ "$TOKEN_READY" = true ]; then
echo "Add this server to Pulse with:"`) {
t.Fatalf("setup script does not gate PBS manual token details on usable token extraction: %s", pbsScript)
}
attemptBannerIndex := strings.Index(pbsScript, `echo "🔄 Attempting auto-registration with Pulse..."`)
authTokenGateIndex := strings.Index(pbsScript, `if [ -n "$PULSE_SETUP_TOKEN" ]; then`)
tokenSkipIndex := strings.Index(pbsScript, `echo "⚠️ Auto-registration skipped: token value unavailable"`)
if attemptBannerIndex == -1 || authTokenGateIndex == -1 || tokenSkipIndex == -1 {
t.Fatalf("setup script missing PBS auto-registration truth markers: %s", pbsScript)
}
if attemptBannerIndex < authTokenGateIndex {
t.Fatalf("setup script prints PBS auto-registration attempt banner before the real request path: %s", pbsScript)
}
if attemptBannerIndex < tokenSkipIndex {
t.Fatalf("setup script prints PBS auto-registration attempt banner before token-unavailable skip handling: %s", pbsScript)
}
if strings.Contains(pbsScript, `elif [ "$TOKEN_READY" != true ]; then
echo "Pulse monitoring token setup completed."`) {
t.Fatalf("setup script lets PBS token-extract failure fall through to completed token setup: %s", pbsScript)
}
if strings.Contains(pbsScript, `echo " Host URL: https://$SERVER_IP:8007"`) {
t.Fatalf("setup script preserved stale PBS runtime-IP host guidance: %s", pbsScript)
}
if strings.Contains(pbsScript, `echo "Manual registration may be required."`) {
t.Fatalf("setup script preserved stale PBS manual-registration token failure guidance: %s", pbsScript)
}
if strings.Contains(pbsScript, `echo "To enable auto-registration, rerun with a valid Pulse setup token"`) {
t.Fatalf("setup script preserved stale split PBS setup-token auth guidance: %s", pbsScript)
}
if !strings.Contains(pbsScript, `echo "⚠️ Auto-registration failed. Finish registration manually in Pulse Settings → Nodes."`) {
t.Fatalf("setup script missing canonical PBS auto-register failure summary: %s", pbsScript)
}
if strings.Count(pbsScript, `echo "📝 Use the token details below in Pulse Settings → Nodes to finish registration."`) < 2 {
t.Fatalf("setup script missing canonical PBS request-failure/manual-response continuity: %s", pbsScript)
}
if strings.Contains(pbsScript, `echo "⚠️ Auto-registration failed. Manual configuration may be needed."`) {
t.Fatalf("setup script preserved stale PBS auto-register failure summary: %s", pbsScript)
}
}
func TestContract_SetupScriptRequiresCanonicalHost(t *testing.T) {
tempDir := t.TempDir()
cfg := &config.Config{
DataPath: tempDir,
ConfigPath: tempDir,
}
handlers := newTestConfigHandlers(t, cfg)
req := httptest.NewRequest(http.MethodGet, "/api/setup-script?type=pve", nil)
rec := httptest.NewRecorder()
handlers.HandleSetupScript(rec, req)
if rec.Code != http.StatusBadRequest {
t.Fatalf("status = %d, want %d", rec.Code, http.StatusBadRequest)
}
if got := strings.TrimSpace(rec.Body.String()); got != "Missing required parameter: host" {
t.Fatalf("body = %q, want canonical missing host guidance", got)
}
}
func TestContract_SetupScriptUsesCanonicalTypeAndHostValidation(t *testing.T) {
tempDir := t.TempDir()
cfg := &config.Config{
DataPath: tempDir,
ConfigPath: tempDir,
}
handlers := newTestConfigHandlers(t, cfg)
invalidTypeReq := httptest.NewRequest(
http.MethodGet,
"/api/setup-script?type=pmg&host=https://node.example.internal:8006&pulse_url=https://pulse.example.com:7655",
nil,
)
invalidTypeRec := httptest.NewRecorder()
handlers.HandleSetupScript(invalidTypeRec, invalidTypeReq)
if invalidTypeRec.Code != http.StatusBadRequest {
t.Fatalf("invalid type status = %d, want %d", invalidTypeRec.Code, http.StatusBadRequest)
}
if got := strings.TrimSpace(invalidTypeRec.Body.String()); got != "type must be 'pve' or 'pbs'" {
t.Fatalf("invalid type body = %q, want canonical type guidance", got)
}
normalizedHostReq := httptest.NewRequest(
http.MethodGet,
"/api/setup-script?type=pve&host=https://pve-node.example.internal&pulse_url=https://pulse.example.com:7655",
nil,
)
normalizedHostRec := httptest.NewRecorder()
handlers.HandleSetupScript(normalizedHostRec, normalizedHostReq)
if normalizedHostRec.Code != http.StatusOK {
t.Fatalf("normalized host status = %d, want %d: %s", normalizedHostRec.Code, http.StatusOK, normalizedHostRec.Body.String())
}
body := normalizedHostRec.Body.String()
if !strings.Contains(body, `SERVER_HOST="https://pve-node.example.internal:8006"`) {
t.Fatalf("normalized host body missing canonical host, got: %s", truncate(body, 500))
}
if !strings.Contains(body, `SETUP_SCRIPT_URL="https://pulse.example.com:7655/api/setup-script?host=https%3A%2F%2Fpve-node.example.internal%3A8006&pulse_url=https%3A%2F%2Fpulse.example.com%3A7655&type=pve"`) {
t.Fatalf("normalized host body missing canonical rerun URL, got: %s", truncate(body, 700))
}
}
func TestContract_SetupScriptRequiresCanonicalPulseURL(t *testing.T) {
tempDir := t.TempDir()
cfg := &config.Config{
DataPath: tempDir,
ConfigPath: tempDir,
}
handlers := newTestConfigHandlers(t, cfg)
req := httptest.NewRequest(http.MethodGet, "/api/setup-script?type=pve&host=https://pve.local:8006", nil)
rec := httptest.NewRecorder()
handlers.HandleSetupScript(rec, req)
if rec.Code != http.StatusBadRequest {
t.Fatalf("status = %d, want %d", rec.Code, http.StatusBadRequest)
}
if got := strings.TrimSpace(rec.Body.String()); got != "Missing required parameter: pulse_url" {
t.Fatalf("body = %q, want canonical missing pulse_url guidance", got)
}
}
func TestContract_SetupScriptUsesCanonicalShellDownloadHeaders(t *testing.T) {
tempDir := t.TempDir()
cfg := &config.Config{
DataPath: tempDir,
ConfigPath: tempDir,
}
handlers := newTestConfigHandlers(t, cfg)
req := httptest.NewRequest(http.MethodGet,
"/api/setup-script?type=pbs&host=https://sentinel-pbs:8007&pulse_url=http://sentinel-url:7656", nil)
rec := httptest.NewRecorder()
handlers.HandleSetupScript(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("status = %d, want %d", rec.Code, http.StatusOK)
}
if got := rec.Header().Get("Content-Type"); got != "text/x-shellscript; charset=utf-8" {
t.Fatalf("setup-script content type = %q, want %q", got, "text/x-shellscript; charset=utf-8")
}
if got := rec.Header().Get("Content-Disposition"); got != "attachment; filename=\"pulse-setup-pbs.sh\"" {
t.Fatalf("setup-script content disposition = %q, want %q", got, "attachment; filename=\"pulse-setup-pbs.sh\"")
}
}
func TestContract_SetupScriptDerivesRenderedServerNameFromCanonicalHost(t *testing.T) {
handlers := newTestConfigHandlers(t, &config.Config{
DataPath: t.TempDir(),
ConfigPath: t.TempDir(),
})
req := httptest.NewRequest(
http.MethodGet,
"/api/setup-script?type=pve&host=https://derived-pve.example.internal:8006&pulse_url=https://pulse.example.com:7655",
nil,
)
rec := httptest.NewRecorder()
handlers.HandleSetupScript(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("status = %d, want %d: %s", rec.Code, http.StatusOK, rec.Body.String())
}
script := rec.Body.String()
if !strings.Contains(script, "# Pulse Monitoring Setup Script for derived-pve.example.internal") {
t.Fatalf("setup script missing derived canonical host label: %s", script)
}
if strings.Contains(script, "# Pulse Monitoring Setup Script for your-server") {
t.Fatalf("setup script preserved placeholder server label for canonical host: %s", script)
}
}
func TestContract_AssignProfileRejectsMissingProfile(t *testing.T) {
tempDir := t.TempDir()
mtp := config.NewMultiTenantPersistence(tempDir)
if _, err := mtp.GetPersistence("default"); err != nil {
t.Fatalf("init default persistence: %v", err)
}
handler := NewConfigProfileHandler(mtp)
body := bytes.NewBufferString(`{"agent_id":"agent-1","profile_id":"missing-profile"}`)
req := httptest.NewRequest(http.MethodPost, "/assignments", body)
rec := httptest.NewRecorder()
handler.ServeHTTP(rec, req)
if rec.Code != http.StatusNotFound {
t.Fatalf("status = %d, want %d: %s", rec.Code, http.StatusNotFound, rec.Body.String())
}
if got := strings.TrimSpace(rec.Body.String()); got != "Profile not found" {
t.Fatalf("body = %q, want %q", got, "Profile not found")
}
}
func TestContract_ResolveLoopbackAwarePublicBaseURLPreservesConfiguredHTTPS(t *testing.T) {
cfg := &config.Config{
PublicURL: "https://public.example.com/base/",
FrontendPort: 7655,
}
req := httptest.NewRequest(http.MethodGet, "/", nil)
req.Host = "127.0.0.1:7655"
if got := resolveLoopbackAwarePublicBaseURL(req, cfg); got != "https://public.example.com/base" {
t.Fatalf("baseURL = %q, want %q", got, "https://public.example.com/base")
}
}
func TestContract_CanonicalPulseMonitorTokenNamePrefersPulseURL(t *testing.T) {
got := buildPulseMonitorTokenName("https://public.example.com/base", "127.0.0.1:7655")
if got != "pulse-public-example-com" {
t.Fatalf("tokenName = %q, want %q", got, "pulse-public-example-com")
}
}
func TestContract_FilterRecoveryPointsForRollupsIncludesNormalizedFilters(t *testing.T) {
verified := true
unverified := false
points := []recovery.RecoveryPoint{
{
ID: "point-1",
Provider: recovery.ProviderKubernetes,
Kind: recovery.KindBackup,
Mode: recovery.ModeRemote,
Outcome: recovery.OutcomeSuccess,
SubjectResourceID: "pod-1",
Verified: &verified,
Display: &recovery.RecoveryPointDisplay{
SubjectLabel: "pod-1",
SubjectType: "pod",
ItemType: "pod",
ClusterLabel: "prod-cluster",
NodeHostLabel: "worker-1",
NamespaceLabel: "default",
RepositoryLabel: "repo-a",
IsWorkload: true,
},
},
{
ID: "point-2",
Provider: recovery.ProviderKubernetes,
Kind: recovery.KindBackup,
Mode: recovery.ModeRemote,
Outcome: recovery.OutcomeFailed,
SubjectResourceID: "pod-2",
Verified: &unverified,
Display: &recovery.RecoveryPointDisplay{
SubjectLabel: "pod-2",
SubjectType: "pod",
ItemType: "pod",
ClusterLabel: "other-cluster",
NodeHostLabel: "worker-2",
NamespaceLabel: "kube-system",
IsWorkload: true,
},
},
}
filtered := filterRecoveryPointsForRollups(points, recovery.ListPointsOptions{
Query: "repo-a",
ItemType: "pod",
ClusterLabel: "prod-cluster",
NodeHostLabel: "worker-1",
NamespaceLabel: "default",
Verification: "verified",
WorkloadOnly: true,
})
if len(filtered) != 1 {
t.Fatalf("len(filtered) = %d, want 1", len(filtered))
}
if got := filtered[0].SubjectResourceID; got != "pod-1" {
t.Fatalf("subjectResourceID = %q, want %q", got, "pod-1")
}
}
func TestContract_ParseRecoveryPlatformQueryPrefersCanonicalPlatformAlias(t *testing.T) {
t.Parallel()
if got := parseRecoveryPlatformQuery(url.Values{
"platform": []string{" truenas "},
"provider": []string{"proxmox-pve"},
}); got != recovery.Provider("truenas") {
t.Fatalf("parseRecoveryPlatformQuery(platform first) = %q, want %q", got, "truenas")
}
if got := parseRecoveryPlatformQuery(url.Values{
"provider": []string{" proxmox-pbs "},
}); got != recovery.Provider("proxmox-pbs") {
t.Fatalf("parseRecoveryPlatformQuery(provider fallback) = %q, want %q", got, "proxmox-pbs")
}
}
func TestContract_RecoveryPointPayloadUsesCanonicalPlatformField(t *testing.T) {
payload := buildRecoveryPointPayload(recovery.RecoveryPoint{
ID: "point-1",
Provider: recovery.Provider("truenas"),
Kind: recovery.Kind("snapshot"),
Mode: recovery.Mode("snapshot"),
Outcome: recovery.Outcome("success"),
})
got, err := json.Marshal(payload)
if err != nil {
t.Fatalf("marshal recovery point payload: %v", err)
}
const want = `{
"id":"point-1",
"platform":"truenas",
"provider":"truenas",
"kind":"snapshot",
"mode":"snapshot",
"outcome":"success"
}`
assertJSONSnapshot(t, got, want)
}
func TestContract_RecoveryPointsMockPathReturnsCanonicalProviderBackedFixtures(t *testing.T) {
previousEnabled := mock.IsMockEnabled()
previousConfig := mock.GetConfig()
t.Cleanup(func() {
if err := mock.SetEnabled(false); err != nil {
t.Errorf("disable mock mode: %v", err)
}
mock.SetMockConfig(previousConfig)
if previousEnabled {
if err := mock.SetEnabled(true); err != nil {
t.Errorf("restore mock mode: %v", err)
}
mock.SetMockConfig(previousConfig)
}
})
t.Setenv("PULSE_MOCK_NODES", "1")
t.Setenv("PULSE_MOCK_VMS_PER_NODE", "0")
t.Setenv("PULSE_MOCK_LXCS_PER_NODE", "0")
t.Setenv("PULSE_MOCK_DOCKER_HOSTS", "0")
t.Setenv("PULSE_MOCK_DOCKER_CONTAINERS", "0")
t.Setenv("PULSE_MOCK_GENERIC_HOSTS", "0")
t.Setenv("PULSE_MOCK_K8S_CLUSTERS", "0")
t.Setenv("PULSE_MOCK_K8S_NODES", "0")
t.Setenv("PULSE_MOCK_K8S_PODS", "0")
t.Setenv("PULSE_MOCK_K8S_DEPLOYMENTS", "0")
if err := mock.SetEnabled(false); err != nil {
t.Fatalf("disable mock mode: %v", err)
}
if err := mock.SetEnabled(true); err != nil {
t.Fatalf("enable mock mode: %v", err)
}
req := httptest.NewRequest(http.MethodGet, "/api/recovery/points?platform=truenas&limit=10", nil)
rec := httptest.NewRecorder()
NewRecoveryHandlers(nil).HandleListPoints(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("expected 200, got %d: %s", rec.Code, rec.Body.String())
}
var resp recoveryPointsResponse
if err := json.Unmarshal(rec.Body.Bytes(), &resp); err != nil {
t.Fatalf("unmarshal recovery points response: %v", err)
}
if len(resp.Data) == 0 {
t.Fatal("expected provider-backed mock recovery points in response")
}
if resp.Data[0].Platform != recovery.Provider("truenas") {
t.Fatalf("platform = %q, want %q", resp.Data[0].Platform, "truenas")
}
if resp.Data[0].Display == nil {
t.Fatal("expected normalized recovery display payload on mock points response")
}
}
func TestContract_RecoveryRollupPayloadUsesCanonicalPlatformsField(t *testing.T) {
payload := buildRecoveryRollupPayload(recovery.ProtectionRollup{
RollupID: "rollup-1",
LastOutcome: recovery.Outcome("success"),
Providers: []recovery.Provider{
recovery.Provider("proxmox-pbs"),
recovery.Provider("truenas"),
},
})
got, err := json.Marshal(payload)
if err != nil {
t.Fatalf("marshal recovery rollup payload: %v", err)
}
const want = `{
"rollupId":"rollup-1",
"lastOutcome":"success",
"platforms":["proxmox-pbs","truenas"],
"providers":["proxmox-pbs","truenas"]
}`
assertJSONSnapshot(t, got, want)
}
func TestContract_BillingStateJSONSnapshot(t *testing.T) {
payload := entitlements.BillingState{
Capabilities: []string{"relay", "mobile_app"},
Limits: map[string]int64{"max_monitored_systems": 10},
MetersEnabled: []string{"api_requests"},
PlanVersion: "cloud_starter",
SubscriptionState: entitlements.SubStateActive,
StripeCustomerID: "cus_123",
StripeSubscriptionID: "sub_123",
StripePriceID: "price_123",
}
got, err := json.Marshal(payload)
if err != nil {
t.Fatalf("marshal billing state: %v", err)
}
const want = `{
"capabilities":["relay","mobile_app"],
"limits":{"max_monitored_systems":10},
"meters_enabled":["api_requests"],
"plan_version":"cloud_starter",
"subscription_state":"active",
"stripe_customer_id":"cus_123",
"stripe_subscription_id":"sub_123",
"stripe_price_id":"price_123"
}`
assertJSONSnapshot(t, got, want)
}
func TestContract_HostedTenantEntitlementsFallbackToDefaultBillingState(t *testing.T) {
baseDir := t.TempDir()
mtp := config.NewMultiTenantPersistence(baseDir)
if _, err := mtp.GetPersistence("default"); err != nil {
t.Fatalf("init default persistence: %v", err)
}
if _, err := mtp.GetPersistence("t-tenant"); err != nil {
t.Fatalf("init tenant persistence: %v", err)
}
store := config.NewFileBillingStore(baseDir)
if err := store.SaveBillingState("default", &entitlements.BillingState{
Capabilities: []string{pkglicensing.FeatureRelay, pkglicensing.FeatureRBAC},
Limits: map[string]int64{"max_monitored_systems": 50},
PlanVersion: "msp_starter",
SubscriptionState: entitlements.SubStateActive,
}); err != nil {
t.Fatalf("save default billing state: %v", err)
}
handlers := NewLicenseHandlers(mtp, true)
ctx := context.WithValue(context.Background(), OrgIDContextKey, "t-tenant")
req := httptest.NewRequest(http.MethodGet, "/api/license/entitlements", nil).WithContext(ctx)
rec := httptest.NewRecorder()
handlers.HandleEntitlements(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("entitlements status=%d, want %d: %s", rec.Code, http.StatusOK, rec.Body.String())
}
var payload EntitlementPayload
if err := json.NewDecoder(rec.Body).Decode(&payload); err != nil {
t.Fatalf("decode entitlements payload: %v", err)
}
if payload.SubscriptionState != string(pkglicensing.SubStateActive) {
t.Fatalf("subscription_state=%q, want %q", payload.SubscriptionState, pkglicensing.SubStateActive)
}
if !sliceContainsString(payload.Capabilities, pkglicensing.FeatureRelay) {
t.Fatalf("expected hosted tenant payload to include %q from default hosted billing state", pkglicensing.FeatureRelay)
}
for _, limit := range payload.Limits {
if limit.Key == pkglicensing.MaxMonitoredSystemsLicenseGateKey {
t.Fatalf("expected retired max_monitored_systems limit to be omitted, got %+v", payload.Limits)
}
}
}
func TestContract_HostedTenantEntitlementRefreshFallsBackToDefaultBillingState(t *testing.T) {
baseDir := t.TempDir()
mtp := config.NewMultiTenantPersistence(baseDir)
if _, err := mtp.GetPersistence("default"); err != nil {
t.Fatalf("init default persistence: %v", err)
}
if _, err := mtp.GetPersistence("t-tenant"); err != nil {
t.Fatalf("init tenant persistence: %v", err)
}
pub, priv, err := ed25519.GenerateKey(nil)
if err != nil {
t.Fatalf("GenerateKey: %v", err)
}
t.Setenv(pkglicensing.TrialActivationPublicKeyEnvVar, base64.StdEncoding.EncodeToString(pub))
refreshServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/api/entitlements/refresh" {
http.NotFound(w, r)
return
}
var req hostedTrialLeaseRefreshRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
t.Fatalf("decode refresh request: %v", err)
}
if req.OrgID != "default" {
t.Fatalf("req.OrgID=%q, want %q", req.OrgID, "default")
}
if req.InstanceHost != "pulse.example.com" {
t.Fatalf("req.InstanceHost=%q, want %q", req.InstanceHost, "pulse.example.com")
}
if req.EntitlementRefreshToken != "etr_hosted_default" {
t.Fatalf("req.EntitlementRefreshToken=%q, want %q", req.EntitlementRefreshToken, "etr_hosted_default")
}
entitlementJWT, err := pkglicensing.SignEntitlementLeaseToken(priv, pkglicensing.EntitlementLeaseClaims{
OrgID: "default",
InstanceHost: "pulse.example.com",
PlanVersion: "msp_starter",
SubscriptionState: pkglicensing.SubStateActive,
Capabilities: []string{
pkglicensing.FeatureRelay,
pkglicensing.FeatureAIAutoFix,
},
})
if err != nil {
t.Fatalf("SignEntitlementLeaseToken: %v", err)
}
w.Header().Set("Content-Type", "application/json")
_ = json.NewEncoder(w).Encode(hostedTrialLeaseRefreshResponse{
EntitlementJWT: entitlementJWT,
})
}))
defer refreshServer.Close()
store := config.NewFileBillingStore(baseDir)
expiredLease, err := pkglicensing.SignEntitlementLeaseToken(priv, pkglicensing.EntitlementLeaseClaims{
OrgID: "default",
InstanceHost: "pulse.example.com",
PlanVersion: "msp_starter",
SubscriptionState: pkglicensing.SubStateActive,
Capabilities: []string{pkglicensing.FeatureRelay},
RegisteredClaims: jwt.RegisteredClaims{
ExpiresAt: jwt.NewNumericDate(time.Now().Add(-time.Hour)),
},
})
if err != nil {
t.Fatalf("SignEntitlementLeaseToken(expired): %v", err)
}
if err := store.SaveBillingState("default", &entitlements.BillingState{
EntitlementJWT: expiredLease,
EntitlementRefreshToken: "etr_hosted_default",
}); err != nil {
t.Fatalf("save default billing state: %v", err)
}
handlers := NewLicenseHandlers(mtp, true, &config.Config{
PublicURL: "https://pulse.example.com",
ProTrialSignupURL: refreshServer.URL + "/start-pro-trial",
})
refreshed, permanent, err := handlers.refreshHostedEntitlementLeaseOnce("t-tenant", nil)
if err != nil {
t.Fatalf("refreshHostedEntitlementLeaseOnce: %v", err)
}
if !refreshed || permanent {
t.Fatalf("refreshed=%v permanent=%v, want refreshed=true permanent=false", refreshed, permanent)
}
state, err := store.GetBillingState("default")
if err != nil {
t.Fatalf("GetBillingState(default): %v", err)
}
if state == nil {
t.Fatal("expected default billing state after hosted tenant refresh")
}
if state.SubscriptionState != entitlements.SubStateActive {
t.Fatalf("subscription_state=%q, want %q", state.SubscriptionState, entitlements.SubStateActive)
}
if state.PlanVersion != "msp_starter" {
t.Fatalf("plan_version=%q, want %q", state.PlanVersion, "msp_starter")
}
if !sliceContainsString(state.Capabilities, pkglicensing.FeatureAIAutoFix) {
t.Fatalf("expected default hosted billing state to include %q after tenant refresh, got %v", pkglicensing.FeatureAIAutoFix, state.Capabilities)
}
}
func TestContract_HostedEntitlementVerifierBridgeUsesCompatibilityEnvAlias(t *testing.T) {
if pkglicensing.HostedEntitlementPublicKeyEnvVar != pkglicensing.TrialActivationPublicKeyEnvVar {
t.Fatalf("hosted entitlement verifier env=%q, want legacy env alias %q", pkglicensing.HostedEntitlementPublicKeyEnvVar, pkglicensing.TrialActivationPublicKeyEnvVar)
}
pub, _, err := ed25519.GenerateKey(nil)
if err != nil {
t.Fatalf("GenerateKey: %v", err)
}
t.Setenv("PULSE_HOSTED_MODE", "true")
t.Setenv(pkglicensing.HostedEntitlementPublicKeyEnvVar, base64.StdEncoding.EncodeToString(pub))
got, err := trialActivationPublicKeyFromLicensing()
if err != nil {
t.Fatalf("trialActivationPublicKeyFromLicensing: %v", err)
}
if !bytes.Equal(got, pub) {
t.Fatal("hosted entitlement verifier bridge did not resolve the compatibility env key")
}
}
func TestContract_EntitlementPayloadMonitoredSystemUsageJSONSnapshot(t *testing.T) {
payload := buildEntitlementPayloadWithUsage(&licenseStatus{
Valid: true,
Tier: pkglicensing.TierPro,
Features: append([]string(nil), pkglicensing.TierFeatures[pkglicensing.TierPro]...),
}, string(pkglicensing.SubStateActive), entitlementUsageSnapshot{
MonitoredSystems: 7,
MonitoredSystemsAvailable: true,
LegacyConnections: legacyConnectionCountsModel{
ProxmoxNodes: 2,
DockerHosts: 1,
KubernetesClusters: 1,
},
}, nil)
got, err := json.Marshal(payload)
if err != nil {
t.Fatalf("marshal entitlement payload: %v", err)
}
const want = `{
"capabilities":["update_alerts","sso","advanced_sso","ai_patrol","relay","mobile_app","push_notifications","long_term_metrics","ai_alerts","ai_autofix","kubernetes_ai","agent_profiles","rbac","audit_logging","advanced_reporting"],
"limits":[],
"subscription_state":"active",
"upgrade_reasons":[],
"tier":"pro",
"hosted_mode":false,
"valid":true,
"is_lifetime":false,
"days_remaining":0,
"trial_eligible":false,
"max_history_days":90,
"legacy_connections":{"proxmox_nodes":2,"docker_hosts":1,"kubernetes_clusters":1},
"has_migration_gap":false
}`
assertJSONSnapshot(t, got, want)
}
func TestContract_SelfHostedCommunityEntitlementsJSONSnapshot(t *testing.T) {
baseDir := t.TempDir()
mtp := config.NewMultiTenantPersistence(baseDir)
handlers := NewLicenseHandlers(mtp, false)
req := httptest.NewRequest(http.MethodGet, "/api/license/entitlements", nil).
WithContext(context.WithValue(context.Background(), OrgIDContextKey, "default"))
rec := httptest.NewRecorder()
handlers.HandleEntitlements(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("entitlements status=%d, want %d: %s", rec.Code, http.StatusOK, rec.Body.String())
}
var payload EntitlementPayload
if err := json.Unmarshal(rec.Body.Bytes(), &payload); err != nil {
t.Fatalf("decode entitlements: %v", err)
}
got, err := json.Marshal(payload)
if err != nil {
t.Fatalf("marshal entitlements payload: %v", err)
}
const want = `{
"capabilities":["update_alerts","sso","advanced_sso","ai_patrol"],
"limits":[],
"subscription_state":"active",
"upgrade_reasons":[
{"key":"mobile_app","reason":"Get Relay so Pulse Mobile can pair with this instance for secure handoff and notifications.","action_url":"https://pulserelay.pro/pricing?utm_source=pulse\u0026utm_medium=app\u0026utm_campaign=upgrade\u0026feature=mobile_app"},
{"key":"push_notifications","reason":"Get Relay so important alerts reach you immediately on mobile instead of waiting for you to reopen Pulse.","action_url":"https://pulserelay.pro/pricing?utm_source=pulse\u0026utm_medium=app\u0026utm_campaign=upgrade\u0026feature=push_notifications"},
{"key":"relay","reason":"Get Relay so Pulse stays reachable securely from anywhere instead of only on the local dashboard.","action_url":"https://pulserelay.pro/pricing?utm_source=pulse\u0026utm_medium=app\u0026utm_campaign=upgrade\u0026feature=relay"},
{"key":"long_term_metrics","reason":"Get Relay for 14 days of history, or Pro for 90 days, so you can see what changed before and after an incident.","action_url":"https://pulserelay.pro/pricing?utm_source=pulse\u0026utm_medium=app\u0026utm_campaign=upgrade\u0026feature=long_term_metrics"},
{"key":"ai_autofix","reason":"Upgrade to Pro so Patrol can investigate issues, handle safe fixes within Patrol mode, and verify the outcome.","action_url":"https://pulserelay.pro/pricing?utm_source=pulse\u0026utm_medium=app\u0026utm_campaign=upgrade\u0026feature=ai_autofix"},
{"key":"ai_alerts","reason":"Upgrade to Pro so Patrol can investigate issues instead of handing you a stack of symptoms.","action_url":"https://pulserelay.pro/pricing?utm_source=pulse\u0026utm_medium=app\u0026utm_campaign=upgrade\u0026feature=ai_alerts"},
{"key":"rbac","reason":"Upgrade to Pro when more than one operator needs safe access boundaries around infrastructure changes.","action_url":"https://pulserelay.pro/pricing?utm_source=pulse\u0026utm_medium=app\u0026utm_campaign=upgrade\u0026feature=rbac"},
{"key":"agent_profiles","reason":"Upgrade to Pro to standardize agent behavior across systems without reconfiguring every install by hand.","action_url":"https://pulserelay.pro/pricing?utm_source=pulse\u0026utm_medium=app\u0026utm_campaign=upgrade\u0026feature=agent_profiles"},
{"key":"audit_logging","reason":"Upgrade to Pro to keep a trustworthy action trail for incident review, accountability, and compliance.","action_url":"https://pulserelay.pro/pricing?utm_source=pulse\u0026utm_medium=app\u0026utm_campaign=upgrade\u0026feature=audit_logging"},
{"key":"advanced_reporting","reason":"Upgrade to Pro to turn live infrastructure state into shareable reports without manual screenshot work.","action_url":"https://pulserelay.pro/pricing?utm_source=pulse\u0026utm_medium=app\u0026utm_campaign=upgrade\u0026feature=advanced_reporting"}
],
"tier":"free",
"hosted_mode":false,
"valid":false,
"is_lifetime":false,
"days_remaining":0,
"trial_eligible":false,
"max_history_days":7,
"overflow_days_remaining":14,
"legacy_connections":{"proxmox_nodes":0,"docker_hosts":0,"kubernetes_clusters":0},
"has_migration_gap":false,
"runtime":{"build":"community","label":"Pulse Community runtime"}
}`
assertJSONSnapshot(t, got, want)
}
func TestContract_SelfHostedCommunityRuntimeCapabilitiesJSONSnapshot(t *testing.T) {
baseDir := t.TempDir()
mtp := config.NewMultiTenantPersistence(baseDir)
handlers := NewLicenseHandlers(mtp, false)
req := httptest.NewRequest(http.MethodGet, "/api/license/runtime-capabilities", nil).
WithContext(context.WithValue(context.Background(), OrgIDContextKey, "default"))
rec := httptest.NewRecorder()
handlers.HandleRuntimeCapabilities(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf(
"runtime capabilities status=%d, want %d: %s",
rec.Code,
http.StatusOK,
rec.Body.String(),
)
}
var payload RuntimeCapabilitiesPayload
if err := json.Unmarshal(rec.Body.Bytes(), &payload); err != nil {
t.Fatalf("decode runtime capabilities: %v", err)
}
got, err := json.Marshal(payload)
if err != nil {
t.Fatalf("marshal runtime capabilities payload: %v", err)
}
const want = `{
"capabilities":["update_alerts","sso","advanced_sso","ai_patrol"],
"limits":[],
"hosted_mode":false,
"max_history_days":7,
"runtime":{"build":"community","label":"Pulse Community runtime"}
}`
assertJSONSnapshot(t, got, want)
}
func TestContract_LicenseHandlersPassRuntimeIdentityToActivationService(t *testing.T) {
source, err := os.ReadFile("licensing_handlers.go")
if err != nil {
t.Fatalf("read licensing_handlers.go: %v", err)
}
text := string(source)
if count := strings.Count(text, "SetRuntimeIdentity(h.currentRuntimeIdentity())"); count < 2 {
t.Fatalf("expected cached and new tenant services to receive runtime identity, count=%d", count)
}
}
func TestContract_EntitlementPayloadMonitoredSystemUsageUnavailableJSONSnapshot(t *testing.T) {
payload := buildEntitlementPayloadWithUsage(&licenseStatus{
Valid: true,
Tier: pkglicensing.TierCloud,
PlanVersion: "cloud_starter",
Features: append([]string(nil), pkglicensing.TierFeatures[pkglicensing.TierPro]...),
}, string(pkglicensing.SubStateActive), entitlementUsageSnapshot{
MonitoredSystemsUnavailableReason: "supplemental_inventory_unsettled",
}, nil)
got, err := json.Marshal(payload)
if err != nil {
t.Fatalf("marshal entitlement payload: %v", err)
}
const want = `{
"capabilities":["update_alerts","sso","advanced_sso","ai_patrol","relay","mobile_app","push_notifications","long_term_metrics","ai_alerts","ai_autofix","kubernetes_ai","agent_profiles","rbac","audit_logging","advanced_reporting"],
"limits":[],
"subscription_state":"active",
"upgrade_reasons":[],
"plan_version":"cloud_starter",
"tier":"cloud",
"hosted_mode":false,
"valid":true,
"is_lifetime":false,
"days_remaining":0,
"trial_eligible":false,
"max_history_days":90,
"legacy_connections":{"proxmox_nodes":0,"docker_hosts":0,"kubernetes_clusters":0},
"has_migration_gap":false
}`
assertJSONSnapshot(t, got, want)
}
func TestContract_EntitlementPayloadLifetimeJSONSnapshot(t *testing.T) {
payload := buildEntitlementPayloadWithUsage(&licenseStatus{
Valid: true,
Tier: pkglicensing.TierLifetime,
PlanVersion: "v5_lifetime_grandfathered",
IsLifetime: true,
Features: append([]string(nil), pkglicensing.TierFeatures[pkglicensing.TierLifetime]...),
MaxGuests: 0,
}, string(pkglicensing.SubStateActive), entitlementUsageSnapshot{
MonitoredSystems: 15,
MonitoredSystemsAvailable: true,
LegacyConnections: legacyConnectionCountsModel{
ProxmoxNodes: 1,
DockerHosts: 1,
},
}, nil)
got, err := json.Marshal(payload)
if err != nil {
t.Fatalf("marshal entitlement payload: %v", err)
}
const want = `{
"capabilities":["update_alerts","sso","advanced_sso","ai_patrol","relay","mobile_app","push_notifications","long_term_metrics","ai_alerts","ai_autofix","kubernetes_ai","agent_profiles","rbac","audit_logging","advanced_reporting"],
"limits":[],
"subscription_state":"active",
"upgrade_reasons":[],
"plan_version":"v5_lifetime_grandfathered",
"tier":"lifetime",
"hosted_mode":false,
"valid":true,
"is_lifetime":true,
"days_remaining":0,
"trial_eligible":false,
"max_history_days":90,
"legacy_connections":{"proxmox_nodes":1,"docker_hosts":1,"kubernetes_clusters":0},
"has_migration_gap":false
}`
assertJSONSnapshot(t, got, want)
}
func TestContract_EntitlementUsageSnapshotWaitsForSettledSupplementalInventory(t *testing.T) {
provider := &contractSupplementalUsageProvider{}
monitor := &monitoring.Monitor{}
monitor.SetResourceStore(unifiedresources.NewMonitorAdapter(nil))
monitor.SetSupplementalRecordsProvider(unifiedresources.SourceTrueNAS, provider)
handlers := &LicenseHandlers{monitor: monitor}
usage := handlers.entitlementUsageSnapshot(context.Background())
if usage.MonitoredSystemsAvailable {
t.Fatalf("expected unsettled supplemental inventory to keep usage unavailable, got %+v", usage)
}
provider.settle(1)
usage = handlers.entitlementUsageSnapshot(context.Background())
if usage.MonitoredSystemsAvailable {
t.Fatalf("expected stale store freshness to keep usage unavailable, got %+v", usage)
}
monitor.SetSupplementalRecordsProvider(unifiedresources.SourceTrueNAS, provider)
usage = handlers.entitlementUsageSnapshot(context.Background())
if !usage.MonitoredSystemsAvailable {
t.Fatalf("expected usage to become available after canonical store rebuild, got %+v", usage)
}
if usage.MonitoredSystems != 1 {
t.Fatalf("MonitoredSystems=%d, want 1", usage.MonitoredSystems)
}
}
func TestContract_MissingLicensePublicKeyActivationErrorGuidesLocalBuilds(t *testing.T) {
msg := userFriendlyActivationError(fmt.Errorf("validate license: %w: signature verification required", pkglicensing.ErrNoPublicKey))
lower := strings.ToLower(msg)
for _, want := range []string{
"official pulse release tarball",
"published docker image",
"same license key",
} {
if !strings.Contains(lower, want) {
t.Fatalf("activation error %q does not contain %q", msg, want)
}
}
for _, forbidden := range []string{
"temporarily unavailable",
"try again later",
"validate license:",
"signature verification",
} {
if strings.Contains(lower, forbidden) {
t.Fatalf("activation error %q contains misleading/internal text %q", msg, forbidden)
}
}
}
func TestContract_LegacyMigrationFallbackStaysUncappedJSONSnapshot(t *testing.T) {
t.Setenv("PULSE_LICENSE_DEV_MODE", "false")
const expectedClientVersion = "6.0.0-rc.1"
grantJWT, grantPublicKey, err := licensetestsupport.GenerateGrantJWTForTesting(pkglicensing.GrantClaims{
LicenseID: "lic_contract_floor",
Tier: "pro",
PlanKey: "legacy_migration_fallback",
State: "active",
Features: []string{"relay"},
IssuedAt: time.Now().Unix(),
ExpiresAt: time.Now().Add(72 * time.Hour).Unix(),
Email: "contract-floor@example.com",
})
if err != nil {
t.Fatalf("generate grant jwt: %v", err)
}
pkglicensing.SetPublicKey(grantPublicKey)
t.Cleanup(func() { pkglicensing.SetPublicKey(nil) })
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/v1/licenses/exchange" {
t.Fatalf("path = %q, want /v1/licenses/exchange", r.URL.Path)
}
var exchangeReq pkglicensing.ExchangeLegacyLicenseRequest
if err := json.NewDecoder(r.Body).Decode(&exchangeReq); err != nil {
t.Fatalf("decode exchange request: %v", err)
}
if exchangeReq.ClientVersion != expectedClientVersion {
t.Fatalf("exchange client version=%q, want %q", exchangeReq.ClientVersion, expectedClientVersion)
}
w.WriteHeader(http.StatusCreated)
_ = json.NewEncoder(w).Encode(pkglicensing.ActivateInstallationResponse{
License: pkglicensing.ActivateResponseLicense{
LicenseID: "lic_contract_floor",
State: "active",
Tier: "pro",
Features: []string{"relay"},
},
Installation: pkglicensing.ActivateResponseInstallation{
InstallationID: "inst_contract_floor",
InstallationToken: "pit_live_contract_floor",
Status: "active",
},
Grant: pkglicensing.GrantEnvelope{
JWT: grantJWT,
JTI: "grant_contract_floor",
ExpiresAt: time.Now().Add(72 * time.Hour).UTC().Format(time.RFC3339),
},
})
}))
defer server.Close()
t.Setenv("PULSE_LICENSE_SERVER_URL", server.URL)
baseDir := t.TempDir()
mtp := config.NewMultiTenantPersistence(baseDir)
cp, err := mtp.GetPersistence("default")
if err != nil {
t.Fatalf("init default persistence: %v", err)
}
persistence, err := pkglicensing.NewPersistence(cp.GetConfigDir())
if err != nil {
t.Fatalf("new persistence: %v", err)
}
legacyJWT, err := licensetestsupport.GenerateLicenseForTesting("contract-floor@example.com", pkglicensing.TierPro, 24*time.Hour)
if err != nil {
t.Fatalf("generate test license: %v", err)
}
if err := persistence.Save(legacyJWT); err != nil {
t.Fatalf("save legacy jwt: %v", err)
}
handlers := NewLicenseHandlers(mtp, false)
handlers.SetRuntimeVersion(expectedClientVersion)
handlers.SetMonitors(buildGrandfatherFloorMonitor(23), nil)
t.Cleanup(handlers.StopAllBackgroundLoops)
ctx := context.WithValue(context.Background(), OrgIDContextKey, "default")
statusReq := httptest.NewRequest(http.MethodGet, "/api/license/status", nil).WithContext(ctx)
statusRec := httptest.NewRecorder()
handlers.HandleLicenseStatus(statusRec, statusReq)
if statusRec.Code != http.StatusOK {
t.Fatalf("status=%d, want %d: %s", statusRec.Code, http.StatusOK, statusRec.Body.String())
}
entReq := httptest.NewRequest(http.MethodGet, "/api/license/entitlements", nil).WithContext(ctx)
entRec := httptest.NewRecorder()
handlers.HandleEntitlements(entRec, entReq)
if entRec.Code != http.StatusOK {
t.Fatalf("entitlements status=%d, want %d: %s", entRec.Code, http.StatusOK, entRec.Body.String())
}
var status pkglicensing.LicenseStatus
if err := json.Unmarshal(statusRec.Body.Bytes(), &status); err != nil {
t.Fatalf("decode status: %v", err)
}
var payload EntitlementPayload
if err := json.Unmarshal(entRec.Body.Bytes(), &payload); err != nil {
t.Fatalf("decode entitlements: %v", err)
}
got, err := json.Marshal(struct {
Status struct {
Tier pkglicensing.Tier `json:"tier"`
PlanVersion string `json:"plan_version"`
Valid bool `json:"valid"`
} `json:"status"`
Entitlements struct {
Tier string `json:"tier"`
PlanVersion string `json:"plan_version"`
SubscriptionState string `json:"subscription_state"`
Limits []pkglicensing.LimitStatus `json:"limits"`
} `json:"entitlements"`
}{
Status: struct {
Tier pkglicensing.Tier `json:"tier"`
PlanVersion string `json:"plan_version"`
Valid bool `json:"valid"`
}{
Tier: status.Tier,
PlanVersion: status.PlanVersion,
Valid: status.Valid,
},
Entitlements: struct {
Tier string `json:"tier"`
PlanVersion string `json:"plan_version"`
SubscriptionState string `json:"subscription_state"`
Limits []pkglicensing.LimitStatus `json:"limits"`
}{
Tier: payload.Tier,
PlanVersion: payload.PlanVersion,
SubscriptionState: payload.SubscriptionState,
Limits: payload.Limits,
},
})
if err != nil {
t.Fatalf("marshal snapshot payload: %v", err)
}
const want = `{
"status":{
"tier":"pro",
"plan_version":"legacy_migration_fallback",
"valid":true
},
"entitlements":{
"tier":"pro",
"plan_version":"legacy_migration_fallback",
"subscription_state":"active",
"limits":[]
}
}`
assertJSONSnapshot(t, got, want)
}
func TestContract_HostedBillingStateFallbackJSONSnapshot(t *testing.T) {
baseDir := t.TempDir()
store := config.NewFileBillingStore(baseDir)
if err := store.SaveBillingState("default", &entitlements.BillingState{
Capabilities: []string{pkglicensing.FeatureRelay, pkglicensing.FeatureRBAC},
Limits: map[string]int64{"max_monitored_systems": 50},
MetersEnabled: []string{},
PlanVersion: "msp_starter",
SubscriptionState: entitlements.SubStateActive,
StripeCustomerID: "cus_hosted",
StripeSubscriptionID: "sub_hosted",
StripePriceID: "price_hosted",
}); err != nil {
t.Fatalf("save default billing state: %v", err)
}
handlers := NewBillingStateHandlers(store, true)
req := httptest.NewRequest(http.MethodGet, "/api/admin/orgs/t-tenant/billing-state", nil)
req.SetPathValue("id", "t-tenant")
rec := httptest.NewRecorder()
handlers.HandleGetBillingState(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("status=%d, want %d: %s", rec.Code, http.StatusOK, rec.Body.String())
}
const want = `{
"capabilities":["relay","rbac"],
"limits":{},
"meters_enabled":[],
"plan_version":"msp_starter",
"subscription_state":"active",
"stripe_customer_id":"cus_hosted",
"stripe_subscription_id":"sub_hosted",
"stripe_price_id":"price_hosted"
}`
assertJSONSnapshot(t, rec.Body.Bytes(), want)
}
func TestContract_MonitoredSystemUsageUnavailableIncludesReason(t *testing.T) {
body := bytes.NewBufferString(`{
"candidate":{
"source":"proxmox",
"name":"tower",
"hostname":"tower.local",
"host_url":"https://tower.local:8006"
}
}`)
req := httptest.NewRequest(http.MethodPost, "/api/license/monitored-system-ledger/preview", body)
rec := httptest.NewRecorder()
router := &Router{}
router.handleMonitoredSystemLedgerPreview(rec, req)
if rec.Code != http.StatusServiceUnavailable {
t.Fatalf("status=%d, want %d: %s", rec.Code, http.StatusServiceUnavailable, rec.Body.String())
}
var payload APIError
if err := json.NewDecoder(rec.Body).Decode(&payload); err != nil {
t.Fatalf("decode unavailable payload: %v", err)
}
payload = payload.NormalizeCollections()
if payload.Code != "monitored_system_usage_unavailable" {
t.Fatalf("code=%q, want monitored_system_usage_unavailable", payload.Code)
}
if got := payload.Details["reason"]; got != monitoring.MonitoredSystemUsageUnavailableMonitorState {
t.Fatalf("details.reason=%q, want %q", got, monitoring.MonitoredSystemUsageUnavailableMonitorState)
}
}
func TestContract_HostReportAdmissionPreservesRestartContinuityAtLimit(t *testing.T) {
setMaxMonitoredSystemsLicenseForTests(t, 1)
cfg := &config.Config{DataPath: t.TempDir()}
report := agentshost.Report{
Agent: agentshost.AgentInfo{
ID: "agent-1",
Version: "6.0.0-rc.1",
},
Host: agentshost.HostInfo{
ID: "machine-1",
MachineID: "machine-1",
Hostname: "host-1.local",
Platform: "linux",
},
Timestamp: time.Now().UTC(),
}
postReport := func(t *testing.T, handler *UnifiedAgentHandlers, input agentshost.Report) *httptest.ResponseRecorder {
t.Helper()
body, err := json.Marshal(input)
if err != nil {
t.Fatalf("marshal host report: %v", err)
}
req := httptest.NewRequest(http.MethodPost, "/api/agents/agent/report", bytes.NewReader(body))
rec := httptest.NewRecorder()
handler.HandleReport(rec, req)
return rec
}
handler, _ := newUnifiedAgentHandlers(t, cfg)
initialRec := postReport(t, handler, report)
if initialRec.Code != http.StatusOK {
t.Fatalf("initial host report should pass, got %d: %s", initialRec.Code, initialRec.Body.String())
}
restartedHandler, _ := newUnifiedAgentHandlers(t, cfg)
restartReport := report
restartReport.Timestamp = report.Timestamp.Add(30 * time.Second)
restartRec := postReport(t, restartedHandler, restartReport)
if restartRec.Code != http.StatusOK {
t.Fatalf("restart continuity report should stay admitted, got %d: %s", restartRec.Code, restartRec.Body.String())
}
newHostReport := agentshost.Report{
Agent: agentshost.AgentInfo{
ID: "agent-2",
Version: "6.0.0-rc.1",
},
Host: agentshost.HostInfo{
ID: "machine-2",
MachineID: "machine-2",
Hostname: "host-2.local",
Platform: "linux",
},
Timestamp: report.Timestamp.Add(time.Minute),
}
newHostRec := postReport(t, restartedHandler, newHostReport)
if newHostRec.Code != http.StatusOK {
t.Fatalf("new host should be admitted because monitored-system caps are retired, got %d: %s", newHostRec.Code, newHostRec.Body.String())
}
}
func TestContract_PlatformConnectionWritesIgnoreUsageUnavailableWithCapsRetired(t *testing.T) {
t.Run("truenas add", func(t *testing.T) {
setTrueNASFeatureForTest(t, true)
setMockModeForTest(t, false)
setMaxMonitoredSystemsLicenseForTests(t, 1)
handler, _, monitor := newTrueNASHandlersForTest(t, nil)
bindUnavailableSupplementalUsageProviderForTest(
t,
monitor,
unifiedresources.SourceTrueNAS,
monitoring.MonitoredSystemUsageUnavailableSupplementalInventoryUnsettled,
)
body := marshalTrueNASRequest(t, map[string]any{
"name": "tower",
"host": "tower.local",
"apiKey": "super-secret",
})
req := httptest.NewRequest(http.MethodPost, "/api/truenas/connections", bytes.NewReader(body))
rec := httptest.NewRecorder()
handler.HandleAdd(rec, req)
if rec.Code != http.StatusCreated {
t.Fatalf("expected 201 with monitored-system caps retired, got %d: %s", rec.Code, rec.Body.String())
}
})
t.Run("vmware add", func(t *testing.T) {
setVMwareFeatureForTest(t, true)
setMockModeForTest(t, false)
setMaxMonitoredSystemsLicenseForTests(t, 1)
handler, _ := newVMwareHandlersForTest(t)
monitor, _, _ := newTestMonitor(t)
handler.getMonitor = func(context.Context) *monitoring.Monitor { return monitor }
bindUnavailableSupplementalUsageProviderForTest(
t,
monitor,
unifiedresources.SourceVMware,
monitoring.MonitoredSystemUsageUnavailableSupplementalInventoryUnsettled,
)
previewRecordsCalled := false
handler.previewRecords = func(context.Context, config.VMwareVCenterInstance) ([]unifiedresources.IngestRecord, error) {
previewRecordsCalled = true
return nil, nil
}
body := marshalVMwareRequest(t, map[string]any{
"name": "lab-vcenter",
"host": "vcsa.lab.local",
"port": 443,
"username": "administrator@vsphere.local",
"password": "super-secret",
"enabled": true,
})
req := httptest.NewRequest(http.MethodPost, "/api/vmware/connections", bytes.NewReader(body))
rec := httptest.NewRecorder()
handler.HandleAdd(rec, req)
if rec.Code != http.StatusCreated {
t.Fatalf("expected 201 with monitored-system caps retired, got %d: %s", rec.Code, rec.Body.String())
}
if previewRecordsCalled {
t.Fatal("expected VMware write not to preview external inventory when monitored-system caps are retired")
}
})
}
func TestContract_DisabledPlatformConnectionWritesBypassUnavailableUsageGate(t *testing.T) {
t.Run("truenas add", func(t *testing.T) {
setTrueNASFeatureForTest(t, true)
setMockModeForTest(t, false)
setMaxMonitoredSystemsLicenseForTests(t, 1)
handler, _, monitor := newTrueNASHandlersForTest(t, nil)
bindUnavailableSupplementalUsageProviderForTest(
t,
monitor,
unifiedresources.SourceTrueNAS,
monitoring.MonitoredSystemUsageUnavailableSupplementalInventoryUnsettled,
)
body := marshalTrueNASRequest(t, map[string]any{
"name": "tower",
"host": "tower.local",
"apiKey": "super-secret",
"enabled": false,
})
req := httptest.NewRequest(http.MethodPost, "/api/truenas/connections", bytes.NewReader(body))
rec := httptest.NewRecorder()
handler.HandleAdd(rec, req)
if rec.Code != http.StatusCreated {
t.Fatalf("status=%d, want %d: %s", rec.Code, http.StatusCreated, rec.Body.String())
}
})
t.Run("truenas update", func(t *testing.T) {
setTrueNASFeatureForTest(t, true)
setMockModeForTest(t, false)
setMaxMonitoredSystemsLicenseForTests(t, 1)
handler, persistence, monitor := newTrueNASHandlersForTest(t, nil)
bindUnavailableSupplementalUsageProviderForTest(
t,
monitor,
unifiedresources.SourceTrueNAS,
monitoring.MonitoredSystemUsageUnavailableSupplementalInventoryRebuildPending,
)
if err := persistence.SaveTrueNASConfig([]config.TrueNASInstance{{
ID: "alpha",
Name: "archive",
Host: "archive.local",
APIKey: "super-secret",
UseHTTPS: true,
Enabled: true,
}}); err != nil {
t.Fatalf("seed truenas config: %v", err)
}
body := marshalTrueNASRequest(t, map[string]any{
"name": "archive",
"host": "archive.local",
"apiKey": "********",
"useHttps": true,
"enabled": false,
})
req := httptest.NewRequest(http.MethodPut, "/api/truenas/connections/alpha", bytes.NewReader(body))
rec := httptest.NewRecorder()
handler.HandleUpdate(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("status=%d, want %d: %s", rec.Code, http.StatusOK, rec.Body.String())
}
})
t.Run("vmware add", func(t *testing.T) {
setVMwareFeatureForTest(t, true)
setMockModeForTest(t, false)
setMaxMonitoredSystemsLicenseForTests(t, 1)
handler, _ := newVMwareHandlersForTest(t)
monitor, _, _ := newTestMonitor(t)
handler.getMonitor = func(context.Context) *monitoring.Monitor { return monitor }
bindUnavailableSupplementalUsageProviderForTest(
t,
monitor,
unifiedresources.SourceVMware,
monitoring.MonitoredSystemUsageUnavailableSupplementalInventoryUnsettled,
)
previewRecordsCalled := false
handler.previewRecords = func(context.Context, config.VMwareVCenterInstance) ([]unifiedresources.IngestRecord, error) {
previewRecordsCalled = true
return nil, nil
}
body := marshalVMwareRequest(t, map[string]any{
"name": "lab-vcenter",
"host": "vcsa.lab.local",
"port": 443,
"username": "administrator@vsphere.local",
"password": "super-secret",
"enabled": false,
})
req := httptest.NewRequest(http.MethodPost, "/api/vmware/connections", bytes.NewReader(body))
rec := httptest.NewRecorder()
handler.HandleAdd(rec, req)
if rec.Code != http.StatusCreated {
t.Fatalf("status=%d, want %d: %s", rec.Code, http.StatusCreated, rec.Body.String())
}
if previewRecordsCalled {
t.Fatal("expected disabled VMware add to bypass external inventory preview")
}
})
t.Run("vmware update", func(t *testing.T) {
setVMwareFeatureForTest(t, true)
setMockModeForTest(t, false)
setMaxMonitoredSystemsLicenseForTests(t, 1)
handler, persistence := newVMwareHandlersForTest(t)
monitor, _, _ := newTestMonitor(t)
handler.getMonitor = func(context.Context) *monitoring.Monitor { return monitor }
bindUnavailableSupplementalUsageProviderForTest(
t,
monitor,
unifiedresources.SourceVMware,
monitoring.MonitoredSystemUsageUnavailableSupplementalInventoryRebuildPending,
)
if err := persistence.SaveVMwareConfig([]config.VMwareVCenterInstance{{
ID: "alpha",
Name: "vc-a",
Host: "vc-a.lab.local",
Port: 443,
Username: "administrator@vsphere.local",
Password: "super-secret",
InsecureSkipVerify: true,
Enabled: true,
}}); err != nil {
t.Fatalf("seed vmware config: %v", err)
}
previewRecordsCalled := false
handler.previewRecords = func(context.Context, config.VMwareVCenterInstance) ([]unifiedresources.IngestRecord, error) {
previewRecordsCalled = true
return nil, nil
}
body := marshalVMwareRequest(t, map[string]any{
"name": "vc-a",
"host": "vc-b.lab.local",
"port": 443,
"username": "administrator@vsphere.local",
"password": "********",
"insecureSkipVerify": true,
"enabled": false,
})
req := httptest.NewRequest(http.MethodPut, "/api/vmware/connections/alpha", bytes.NewReader(body))
rec := httptest.NewRecorder()
handler.HandleUpdate(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("status=%d, want %d: %s", rec.Code, http.StatusOK, rec.Body.String())
}
if previewRecordsCalled {
t.Fatal("expected disabled VMware update to bypass external inventory preview")
}
})
}
func TestContract_PlatformConnectionPreviewPreservesCanonicalEnabledDefaults(t *testing.T) {
t.Run("truenas new preview defaults omitted enabled to active", func(t *testing.T) {
setTrueNASFeatureForTest(t, true)
handler, _, monitor := newTrueNASHandlersForTest(t, nil)
registry := unifiedresources.NewRegistry(nil)
registry.IngestRecords(unifiedresources.SourceAgent, []unifiedresources.IngestRecord{
{
SourceID: "host-1",
Resource: unifiedresources.Resource{
ID: "host-1",
Type: unifiedresources.ResourceTypeAgent,
Name: "tower.local",
Status: unifiedresources.StatusOnline,
Agent: &unifiedresources.AgentData{
AgentID: "agent-1",
Hostname: "tower.local",
MachineID: "machine-1",
},
Identity: unifiedresources.ResourceIdentity{
MachineID: "machine-1",
Hostnames: []string{"tower.local"},
},
},
},
})
setUnexportedField(t, monitor, "resourceStore", monitoring.ResourceStoreInterface(unifiedresources.NewMonitorAdapter(registry)))
body := marshalTrueNASRequest(t, map[string]any{
"name": "tower",
"host": "tower.local",
"apiKey": "super-secret",
})
req := httptest.NewRequest(http.MethodPost, "/api/truenas/connections/preview", bytes.NewReader(body))
rec := httptest.NewRecorder()
handler.HandlePreviewConnection(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("expected 200, got %d: %s", rec.Code, rec.Body.String())
}
var preview MonitoredSystemLedgerPreviewResponse
if err := json.NewDecoder(rec.Body).Decode(&preview); err != nil {
t.Fatalf("decode preview response: %v", err)
}
if preview.Effect != "attaches_existing" {
t.Fatalf("Effect = %q, want attaches_existing", preview.Effect)
}
})
t.Run("vmware saved preview preserves stored enabled when omitted", func(t *testing.T) {
setVMwareFeatureForTest(t, true)
handler, persistence := newVMwareHandlersForTest(t)
monitor, _, _ := newTestMonitor(t)
handler.getMonitor = func(context.Context) *monitoring.Monitor { return monitor }
if err := persistence.SaveVMwareConfig([]config.VMwareVCenterInstance{
{
ID: "conn-1",
Name: "lab-vcenter",
Host: "vcsa.lab.local",
Username: "administrator@vsphere.local",
Password: "super-secret",
Enabled: true,
},
}); err != nil {
t.Fatalf("seed vmware config: %v", err)
}
previewRecordsCalled := false
handler.previewRecords = func(context.Context, config.VMwareVCenterInstance) ([]unifiedresources.IngestRecord, error) {
previewRecordsCalled = true
return []unifiedresources.IngestRecord{
{
SourceID: "vc-1:host:host-101",
Resource: unifiedresources.Resource{
Type: unifiedresources.ResourceTypeAgent,
Name: "esxi-01.lab.local",
Status: unifiedresources.StatusOnline,
VMware: &unifiedresources.VMwareData{
ConnectionID: "conn-1",
ConnectionName: "lab-vcenter",
ManagedObjectID: "host-101",
EntityType: "host",
HostUUID: "uuid-host-1",
},
},
Identity: unifiedresources.ResourceIdentity{
DMIUUID: "uuid-host-1",
Hostnames: []string{"esxi-01.lab.local"},
},
},
}, nil
}
body := marshalVMwareRequest(t, map[string]any{
"host": "edited.lab.local",
"username": "operator@vsphere.local",
"password": "********",
})
req := httptest.NewRequest(http.MethodPost, "/api/vmware/connections/conn-1/preview", bytes.NewReader(body))
rec := httptest.NewRecorder()
handler.HandlePreviewSavedConnection(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("expected 200, got %d: %s", rec.Code, rec.Body.String())
}
if !previewRecordsCalled {
t.Fatal("expected saved VMware preview with omitted enabled to stay on the active preview path")
}
})
}
func TestContract_DemoModeCommercialSurfacePolicy(t *testing.T) {
t.Run("hidden routes return not found", func(t *testing.T) {
next := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
t.Fatalf("hidden commercial route should not reach downstream handler: %s %s", r.Method, r.URL.Path)
})
handler := DemoModeMiddleware(&config.Config{DemoMode: true}, next)
testCases := []struct {
method string
path string
}{
{method: http.MethodGet, path: "/api/license/status"},
{method: http.MethodGet, path: "/api/license/features"},
{method: http.MethodGet, path: "/api/license/commercial-posture"},
{method: http.MethodGet, path: "/api/license/entitlements"},
{method: http.MethodPost, path: "/api/license/activate"},
{method: http.MethodPost, path: "/api/license/clear"},
{method: http.MethodGet, path: "/api/license/monitored-system-ledger"},
{method: http.MethodPost, path: "/api/license/monitored-system-ledger/explain"},
{method: http.MethodPost, path: "/api/license/monitored-system-ledger/preview"},
{method: http.MethodPost, path: "/api/truenas/connections/preview"},
{method: http.MethodPost, path: "/api/truenas/connections/conn-1/preview"},
{method: http.MethodPost, path: "/api/vmware/connections/preview"},
{method: http.MethodPost, path: "/api/vmware/connections/conn-1/preview"},
{method: http.MethodGet, path: "/api/admin/orgs/t-tenant/billing-state"},
{method: http.MethodPut, path: "/api/admin/orgs/t-tenant/billing-state"},
{method: http.MethodGet, path: "/api/diagnostics"},
{method: http.MethodPost, path: "/api/diagnostics/docker/prepare-token"},
{method: http.MethodGet, path: "/api/logs/stream"},
{method: http.MethodGet, path: "/api/logs/download"},
{method: http.MethodGet, path: "/api/logs/level"},
{method: http.MethodPost, path: "/api/logs/level"},
{method: http.MethodGet, path: "/api/admin/users"},
{method: http.MethodGet, path: "/api/admin/users/"},
{method: http.MethodHead, path: "/api/admin/users"},
{method: http.MethodGet, path: "/api/discover"},
{method: http.MethodGet, path: "/api/discover/"},
{method: http.MethodHead, path: "/api/discover"},
{method: http.MethodGet, path: licensePurchaseStartPath},
}
for _, tc := range testCases {
req := httptest.NewRequest(tc.method, tc.path, nil)
rec := httptest.NewRecorder()
handler.ServeHTTP(rec, req)
if rec.Code != http.StatusNotFound {
t.Fatalf("%s %s status=%d, want %d: %s", tc.method, tc.path, rec.Code, http.StatusNotFound, rec.Body.String())
}
}
})
t.Run("runtime capabilities stay available but sanitize public demo limit details", func(t *testing.T) {
t.Setenv("PULSE_LICENSE_DEV_MODE", "true")
handlers := createTestHandler(t)
handlers.SetConfig(&config.Config{DemoMode: true})
licenseKey, err := licensetestsupport.GenerateLicenseForTesting("contract-demo@example.com", pkglicensing.TierPro, 24*time.Hour)
if err != nil {
t.Fatalf("GenerateLicenseForTesting: %v", err)
}
if _, err := handlers.Service(context.Background()).Activate(licenseKey); err != nil {
t.Fatalf("Activate() error = %v", err)
}
req := httptest.NewRequest(http.MethodGet, "/api/license/runtime-capabilities", nil)
rec := httptest.NewRecorder()
handlers.HandleRuntimeCapabilities(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("status=%d, want %d: %s", rec.Code, http.StatusOK, rec.Body.String())
}
var payload RuntimeCapabilitiesPayload
if err := json.NewDecoder(rec.Body).Decode(&payload); err != nil {
t.Fatalf("decode payload: %v", err)
}
if len(payload.Capabilities) == 0 {
t.Fatalf("expected sanitized runtime capabilities to preserve capabilities, got %+v", payload)
}
if sliceContainsString(payload.Capabilities, pkglicensing.FeatureDemoFixtures) {
t.Fatalf("public demo runtime capabilities leaked internal feature %q: %v", pkglicensing.FeatureDemoFixtures, payload.Capabilities)
}
for _, limit := range payload.Limits {
if limit.Limit != 0 || limit.Current != 0 || limit.State != "ok" {
t.Fatalf("sanitized limit=%+v, want limit=0 current=0 state=ok", limit)
}
}
})
}
func TestContract_ReleaseDemoFixtureRuntimeGuardrailsRemainCanonical(t *testing.T) {
requiredSnippets := map[string][]string{
"licensing_handlers.go": {
"func (h *LicenseHandlers) syncReleaseDemoFixtureRuntime(orgID string, service *licenseService) {",
"if !shouldEnforceReleaseDemoFixtureRuntime() {",
"authorized := h.demoFixturesAuthorized(service)",
"mock.SetReleaseFixturesAuthorized(authorized)",
"if !wantsMockFixturesFromEnv() {",
},
"release_demo_fixtures_dev.go": {
"//go:build !release",
"return false",
},
"release_demo_fixtures_release.go": {
"//go:build release",
"return true",
},
}
for file, snippets := range requiredSnippets {
data, err := os.ReadFile(file)
if err != nil {
t.Fatalf("failed to read %s: %v", file, err)
}
source := string(data)
for _, snippet := range snippets {
if !strings.Contains(source, snippet) {
t.Fatalf("%s must contain %q", file, snippet)
}
}
}
}
func TestContract_SelfHostedPurchaseHandoffJSONSnapshot(t *testing.T) {
handler := createTestHandler(t)
handler.SetConfig(&config.Config{PublicURL: "https://pulse.example.com"})
var capturedReq struct {
Feature string `json:"feature"`
SuccessURL string `json:"success_url"`
CancelURL string `json:"cancel_url"`
PurchaseReturnJTI string `json:"purchase_return_jti"`
}
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/v1/checkout/portal-handoff" {
t.Fatalf("unexpected path %q", r.URL.Path)
}
if err := json.NewDecoder(r.Body).Decode(&capturedReq); err != nil {
t.Fatalf("decode checkout portal handoff request: %v", err)
}
w.Header().Set("Content-Type", "application/json")
_ = json.NewEncoder(w).Encode(map[string]any{
"portal_handoff_id": "cph_placeholder",
"feature": capturedReq.Feature,
})
}))
defer server.Close()
t.Setenv("PULSE_LICENSE_SERVER_URL", server.URL)
req := httptest.NewRequest(
http.MethodGet,
"https://pulse.example.com"+licensePurchaseStartPath+
"?feature=max_monitored_systems&utm_content=legacy-bookmark",
nil,
)
rec := httptest.NewRecorder()
handler.HandleCheckoutStart(rec, req)
if rec.Code != http.StatusSeeOther {
t.Fatalf("status=%d, want %d: %s", rec.Code, http.StatusSeeOther, rec.Body.String())
}
location := rec.Header().Get("Location")
redirectURL, err := url.Parse(location)
if err != nil {
t.Fatalf("parse redirect location: %v", err)
}
if redirectURL.Scheme != "https" || redirectURL.Host != "cloud.pulserelay.pro" || redirectURL.Path != "/portal" {
t.Fatalf("redirect location=%q, want Pulse Account portal", location)
}
if got := redirectURL.Query().Get("feature"); got != "" {
t.Fatalf("feature=%q, want omitted portal query", got)
}
if got := redirectURL.Query().Get("return_url"); got != "" {
t.Fatalf("return_url=%q, want omitted portal query", got)
}
if got := redirectURL.Query().Get("purchase_return_token"); got != "" {
t.Fatalf("purchase_return_token=%q, want omitted portal query", got)
}
if got := redirectURL.Query().Get("purchase_handoff_url"); got != "" {
t.Fatalf("purchase_handoff_url=%q, want omitted portal query", got)
}
if got := redirectURL.Query().Get("portal_handoff_id"); got != "cph_placeholder" {
t.Fatalf("portal_handoff_id=%q, want cph_placeholder", got)
}
activationURL, err := url.Parse(capturedReq.SuccessURL)
if err != nil {
t.Fatalf("parse success_url: %v", err)
}
returnToken := strings.TrimSpace(activationURL.Query().Get(licensePurchaseReturnTokenField))
if returnToken == "" {
t.Fatal("expected signed purchase_return_token in success_url")
}
for _, entitlementField := range []string{
"entitlements",
"runtime",
"commercial_posture",
"subscription_state",
"tier",
"plan_version",
} {
if got := activationURL.Query().Get(entitlementField); got != "" {
t.Fatalf("success_url query %s=%q, want omitted checkout UX metadata only", entitlementField, got)
}
}
signingKey, err := handler.purchaseReturnSigningKey()
if err != nil {
t.Fatalf("purchaseReturnSigningKey: %v", err)
}
claims, err := verifyPurchaseReturnTokenFromLicensing(returnToken, signingKey, "pulse.example.com", time.Now().UTC())
if err != nil {
t.Fatalf("verifyPurchaseReturnTokenFromLicensing: %v", err)
}
if claims.Feature != "self_hosted_plan" {
t.Fatalf("claims.Feature=%q, want self_hosted_plan", claims.Feature)
}
if claims.OrgID != "default" {
t.Fatalf("claims.OrgID=%q, want default", claims.OrgID)
}
if capturedReq.PurchaseReturnJTI != claims.ID {
t.Fatalf("purchase_return_jti=%q, want %q", capturedReq.PurchaseReturnJTI, claims.ID)
}
activationQuery := activationURL.Query()
activationQuery.Set(licensePurchaseReturnTokenField, "placeholder")
activationURL.RawQuery = activationQuery.Encode()
got, err := json.Marshal(struct {
PortalUtmContent string `json:"portal_utm_content"`
PortalHandoffID string `json:"portal_handoff_id"`
IntentFeature string `json:"intent_feature"`
PurchaseReturnJTI string `json:"purchase_return_jti"`
SuccessURLTemplate string `json:"success_url_template"`
CancelURL string `json:"cancel_url"`
}{
PortalUtmContent: redirectURL.Query().Get("utm_content"),
PortalHandoffID: redirectURL.Query().Get("portal_handoff_id"),
IntentFeature: capturedReq.Feature,
PurchaseReturnJTI: "placeholder",
SuccessURLTemplate: activationURL.String(),
CancelURL: capturedReq.CancelURL,
})
if err != nil {
t.Fatalf("marshal normalized handoff snapshot: %v", err)
}
const want = `{
"portal_utm_content":"legacy-bookmark",
"portal_handoff_id":"cph_placeholder",
"intent_feature":"self_hosted_plan",
"purchase_return_jti":"placeholder",
"success_url_template":"https://pulse.example.com/auth/license-purchase-activate?purchase_return_token=placeholder\u0026session_id=%7BCHECKOUT_SESSION_ID%7D",
"cancel_url":"https://pulse.example.com/settings/system/billing/plan?intent=self_hosted_plan\u0026purchase=cancelled"
}`
assertJSONSnapshot(t, got, want)
}
func TestContract_SelfHostedPurchaseHandoffUnavailableJSONSnapshot(t *testing.T) {
handler := createTestHandler(t)
handler.SetConfig(&config.Config{PublicURL: "https://pulse.example.com"})
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/v1/checkout/portal-handoff" {
t.Fatalf("unexpected path %q", r.URL.Path)
}
http.Error(w, "portal handoff unavailable", http.StatusBadGateway)
}))
defer server.Close()
t.Setenv("PULSE_LICENSE_SERVER_URL", server.URL)
req := httptest.NewRequest(
http.MethodGet,
"https://pulse.example.com"+licensePurchaseStartPath+"?feature=max_monitored_systems",
nil,
)
rec := httptest.NewRecorder()
handler.HandleCheckoutStart(rec, req)
got, err := json.MarshalIndent(struct {
StatusCode int `json:"status_code"`
TitlePresent bool `json:"title_present"`
RetryMessagePresent bool `json:"retry_message_present"`
OwnedRedirectPath bool `json:"owned_redirect_path"`
}{
StatusCode: rec.Code,
TitlePresent: strings.Contains(rec.Body.String(), "Pulse Account unavailable"),
RetryMessagePresent: strings.Contains(rec.Body.String(), "Retry from this instance in a moment"),
OwnedRedirectPath: strings.Contains(rec.Body.String(), "/settings/system/billing/plan?intent=self_hosted_plan&purchase=unavailable"),
}, "", "\t")
if err != nil {
t.Fatalf("marshal unavailable handoff snapshot: %v", err)
}
const want = `{
"status_code": 503,
"title_present": true,
"retry_message_present": true,
"owned_redirect_path": true
}`
assertJSONSnapshot(t, got, want)
}
func TestContract_SelfHostedPurchaseActivationBridgeCopyStaysPlanOwned(t *testing.T) {
continueRec := httptest.NewRecorder()
writeLicensePurchaseActivationContinuePage(
continueRec,
"session_contract",
"token_contract",
"self_hosted_plan",
"portal_handoff_contract",
)
successRec := httptest.NewRecorder()
writeLicensePurchaseActivationSuccessPage(successRec, "self_hosted_plan")
failureRec := httptest.NewRecorder()
writeLicensePurchaseActivationFailurePage(
failureRec,
http.StatusConflict,
"self_hosted_plan",
selfHostedBillingPurchaseExpired,
"Reopen the upgrade flow from Plans in Pulse.",
)
got, err := json.MarshalIndent(struct {
ContinueTitle bool `json:"continue_title"`
ContinueAction bool `json:"continue_action"`
ContinueLegacyBrand bool `json:"continue_legacy_brand"`
SuccessTitle bool `json:"success_title"`
SuccessReturnLabel bool `json:"success_return_label"`
SuccessLegacyBrand bool `json:"success_legacy_brand"`
FailureTitle bool `json:"failure_title"`
FailureOpenLabel bool `json:"failure_open_label"`
FailureGuidance bool `json:"failure_guidance"`
FailureLegacyBrand bool `json:"failure_legacy_brand"`
}{
ContinueTitle: strings.Contains(continueRec.Body.String(), "Finalizing plan upgrade"),
ContinueAction: strings.Contains(continueRec.Body.String(), "Continue to Plans"),
ContinueLegacyBrand: strings.Contains(continueRec.Body.String(), "Pulse Pro"),
SuccessTitle: strings.Contains(successRec.Body.String(), "Plan activated"),
SuccessReturnLabel: strings.Contains(successRec.Body.String(), "Return to Plans"),
SuccessLegacyBrand: strings.Contains(successRec.Body.String(), "Pulse Pro"),
FailureTitle: strings.Contains(failureRec.Body.String(), "Plan activation needs attention"),
FailureOpenLabel: strings.Contains(failureRec.Body.String(), "Open Plans"),
FailureGuidance: strings.Contains(failureRec.Body.String(), "Reopen the upgrade flow from Plans in Pulse."),
FailureLegacyBrand: strings.Contains(failureRec.Body.String(), "Pulse Pro"),
}, "", "\t")
if err != nil {
t.Fatalf("marshal activation bridge snapshot: %v", err)
}
const want = `{
"continue_title": true,
"continue_action": true,
"continue_legacy_brand": false,
"success_title": true,
"success_return_label": true,
"success_legacy_brand": false,
"failure_title": true,
"failure_open_label": true,
"failure_guidance": true,
"failure_legacy_brand": false
}`
assertJSONSnapshot(t, got, want)
}
func TestContract_SelfHostedPurchaseHandoffRejectsInsecureNonLoopbackCallbackURL(t *testing.T) {
handler := createTestHandler(t)
req := httptest.NewRequest(
http.MethodGet,
"http://192.168.1.25:7655"+licensePurchaseStartPath+"?feature=max_monitored_systems",
nil,
)
req.Host = "192.168.1.25:7655"
req.RemoteAddr = "192.168.1.50:54321"
rec := httptest.NewRecorder()
handler.HandleCheckoutStart(rec, req)
got, err := json.MarshalIndent(struct {
StatusCode int `json:"status_code"`
TitlePresent bool `json:"title_present"`
SecureFailureMessage bool `json:"secure_failure_message"`
OwnedRedirectPath bool `json:"owned_redirect_path"`
}{
StatusCode: rec.Code,
TitlePresent: strings.Contains(rec.Body.String(), "Pulse Account unavailable"),
SecureFailureMessage: strings.Contains(rec.Body.String(), "Pulse could not open Pulse Account from this instance right now"),
OwnedRedirectPath: strings.Contains(rec.Body.String(), "/settings/system/billing/plan?intent=self_hosted_plan&purchase=unavailable"),
}, "", "\t")
if err != nil {
t.Fatalf("marshal insecure handoff snapshot: %v", err)
}
const want = `{
"status_code": 503,
"title_present": true,
"secure_failure_message": true,
"owned_redirect_path": true
}`
assertJSONSnapshot(t, got, want)
}
func TestContract_HandoffExchangeJSONSnapshot(t *testing.T) {
key := []byte("test-handoff-key")
configDir := t.TempDir()
secretsDir := filepath.Join(configDir, "secrets")
if err := os.MkdirAll(secretsDir, 0o755); err != nil {
t.Fatalf("MkdirAll() error = %v", err)
}
if err := os.WriteFile(filepath.Join(secretsDir, "handoff.key"), key, 0o600); err != nil {
t.Fatalf("WriteFile() error = %v", err)
}
handler := HandleHandoffExchange(configDir)
tenantID := "tenant-contract"
saveHandoffTestOrganization(t, configDir, &models.Organization{
ID: tenantID,
DisplayName: "Contract Tenant",
Status: models.OrgStatusActive,
CreatedAt: time.Now().UTC(),
OwnerUserID: "operator.owner+mixed@pulserelay.pro",
})
t.Setenv("PULSE_TENANT_ID", "")
token := signHandoffToken(t, key, cloudHandoffClaims{
AccountID: "acct-contract",
Email: "Operator.Owner+Mixed@PulseRelay.Pro",
Role: "owner",
RegisteredClaims: jwt.RegisteredClaims{
ID: "jti-contract",
Subject: "user-contract",
Issuer: cloudHandoffIssuer,
Audience: jwt.ClaimStrings{tenantID},
ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Hour)),
},
})
req := httptest.NewRequest(http.MethodPost, "/api/cloud/handoff/exchange?token="+token+"&format=json", nil)
req.Host = tenantID + ".cloud.pulserelay.pro"
req.Header.Set("Accept", "application/json")
req.Header.Set("X-Forwarded-Host", req.Host)
req.Header.Set("X-Forwarded-For", "127.0.0.1")
req.RemoteAddr = "127.0.0.1:1234"
rec := httptest.NewRecorder()
handler(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("status=%d, want %d: %s", rec.Code, http.StatusOK, rec.Body.String())
}
const want = `{
"account_id":"acct-contract",
"email":"operator.owner+mixed@pulserelay.pro",
"exp":"placeholder",
"jti":"jti-contract",
"ok":true,
"role":"owner",
"tenant_id":"tenant-contract",
"user_id":"user-contract"
}`
var payload map[string]any
if err := json.Unmarshal(rec.Body.Bytes(), &payload); err != nil {
t.Fatalf("decode handoff payload: %v", err)
}
if _, ok := payload["exp"].(string); !ok {
t.Fatalf("exp missing or not a string: %+v", payload)
}
payload["exp"] = "placeholder"
got, err := json.Marshal(payload)
if err != nil {
t.Fatalf("marshal normalized handoff payload: %v", err)
}
assertJSONSnapshot(t, got, want)
}
func TestContract_TenantAIServiceAvoidsSnapshotProviderBridge(t *testing.T) {
tmp := t.TempDir()
mtp := config.NewMultiTenantPersistence(tmp)
defaultMonitor, _, _ := newTestMonitor(t)
tenantAdapter := unifiedresources.NewMonitorAdapter(unifiedresources.NewRegistry(nil))
tenantMonitor := &monitoring.Monitor{}
tenantMonitor.SetResourceStore(tenantAdapter)
mtm := &monitoring.MultiTenantMonitor{}
setUnexportedField(t, mtm, "monitors", map[string]*monitoring.Monitor{
"default": defaultMonitor,
"tenant-1": tenantMonitor,
})
handler := NewAISettingsHandler(mtp, mtm, nil)
handler.SetStateProvider(defaultMonitor)
ctx := context.WithValue(context.Background(), OrgIDContextKey, "tenant-1")
svc := handler.GetAIService(ctx)
if svc == nil {
t.Fatal("expected tenant AI service")
}
if svc.GetStateProvider() != nil {
t.Fatal("expected tenant AI service to avoid snapshot provider bridge")
}
if svc.GetPatrolService() == nil {
t.Fatal("expected tenant patrol service to initialize from canonical providers")
}
}
func TestContract_HandoffExchangeTargetPathIsSignedAndLocalOnly(t *testing.T) {
source, err := os.ReadFile(filepath.Clean("cloud_handoff_handlers.go"))
if err != nil {
t.Fatalf("read cloud_handoff_handlers.go: %v", err)
}
text := string(source)
for _, required := range []string{
"TargetPath string `json:\"target_path,omitempty\"`",
"sanitizeCloudHandoffTargetPath(claims.TargetPath)",
"http.Redirect(w, r, targetPath, http.StatusTemporaryRedirect)",
"strings.HasPrefix(raw, \"//\")",
"parsed.IsAbs() || parsed.Host != \"\"",
} {
if !strings.Contains(text, required) {
t.Fatalf("cloud handoff exchange must preserve signed local target-path contract: missing %q", required)
}
}
for _, forbidden := range []string{
"r.FormValue(\"target_path\")",
"r.URL.Query().Get(\"target_path\")",
"r.FormValue(\"return_to\")",
"r.URL.Query().Get(\"return_to\")",
} {
if strings.Contains(text, forbidden) {
t.Fatalf("cloud handoff exchange must not accept unsigned redirect targets: found %q", forbidden)
}
}
}
func TestContract_HostedCloudHandoffUsesExistingTenantOrganizationMembership(t *testing.T) {
key := []byte("test-handoff-key")
configDir := t.TempDir()
resetSessionStoreForTests()
t.Cleanup(resetSessionStoreForTests)
resetCSRFStoreForTests()
t.Cleanup(resetCSRFStoreForTests)
InitSessionStore(configDir)
InitCSRFStore(configDir)
secretsDir := filepath.Join(configDir, "secrets")
if err := os.MkdirAll(secretsDir, 0o755); err != nil {
t.Fatalf("mkdir secrets dir: %v", err)
}
if err := os.WriteFile(filepath.Join(secretsDir, "handoff.key"), key, 0o600); err != nil {
t.Fatalf("write handoff key: %v", err)
}
tenantID := "tenant-contract-membership"
mtp := saveHandoffTestOrganization(t, configDir, &models.Organization{
ID: tenantID,
DisplayName: "Contract Membership",
Status: models.OrgStatusActive,
CreatedAt: time.Now().UTC(),
OwnerUserID: "legacy-owner@example.com",
Members: []models.OrganizationMember{
{UserID: "legacy-owner@example.com", Role: models.OrgRoleOwner, AddedAt: time.Now().UTC()},
{UserID: "courtmanr@gmail.com", Role: models.OrgRoleOwner, AddedAt: time.Now().UTC()},
},
})
token := signHandoffToken(t, key, cloudHandoffClaims{
AccountID: "acct-contract-membership",
Email: "courtmanr@gmail.com",
Role: "owner",
RegisteredClaims: jwt.RegisteredClaims{
ID: "jti-contract-membership",
Subject: "user-contract-membership",
Issuer: cloudHandoffIssuer,
Audience: jwt.ClaimStrings{tenantID},
ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Hour)),
},
})
form := url.Values{}
form.Set("token", token)
t.Setenv("PULSE_HOSTED_MODE", "true")
t.Setenv("PULSE_TENANT_ID", "")
t.Setenv("PULSE_PUBLIC_URL", "")
req := httptest.NewRequest(http.MethodPost, "/api/cloud/handoff/exchange?format=json", strings.NewReader(form.Encode()))
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
req.Header.Set("Accept", "application/json")
req.Host = tenantID + ".cloud.pulserelay.pro"
req.RemoteAddr = "198.51.100.20:1234"
rec := httptest.NewRecorder()
HandleHandoffExchange(configDir).ServeHTTP(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("status=%d, want %d: %s", rec.Code, http.StatusOK, rec.Body.String())
}
org, err := mtp.LoadOrganization(tenantID)
if err != nil {
t.Fatalf("load organization: %v", err)
}
if org.OwnerUserID != "legacy-owner@example.com" {
t.Fatalf("ownerUserID=%q, want %q", org.OwnerUserID, "legacy-owner@example.com")
}
if got := org.GetMemberRole("user-contract-membership"); got != models.OrgRoleOwner {
t.Fatalf("stable member role=%q, want %q", got, models.OrgRoleOwner)
}
if got := org.Members[1].Email; got != "courtmanr@gmail.com" {
t.Fatalf("canonicalized member email=%q, want %q", got, "courtmanr@gmail.com")
}
}
func TestContract_HostedDirectCloudHandoffUsesExistingTenantMembership(t *testing.T) {
key := []byte("test-direct-handoff-key")
configDir := t.TempDir()
resetPersistentAuthStoresForTests()
t.Cleanup(resetPersistentAuthStoresForTests)
if err := os.WriteFile(filepath.Join(configDir, cloudauth.HandoffKeyFile), key, 0o600); err != nil {
t.Fatalf("write direct handoff key: %v", err)
}
tenantID := "tenant-direct-contract"
mtp := saveHandoffTestOrganization(t, configDir, &models.Organization{
ID: tenantID,
DisplayName: "Direct Contract Membership",
Status: models.OrgStatusActive,
CreatedAt: time.Now().UTC(),
OwnerUserID: "legacy-owner@example.com",
Members: []models.OrganizationMember{
{UserID: "legacy-owner@example.com", Role: models.OrgRoleOwner, AddedAt: time.Now().UTC()},
{UserID: "courtmanr@gmail.com", Role: models.OrgRoleOwner, AddedAt: time.Now().UTC()},
},
})
token, err := cloudauth.SignWithClaims(key, cloudauth.Claims{
Email: "courtmanr@gmail.com",
TenantID: tenantID,
AccountID: "acct-direct-contract",
UserID: "user-direct-contract",
Role: "owner",
}, time.Hour)
if err != nil {
t.Fatalf("sign direct handoff claims: %v", err)
}
req := httptest.NewRequest(http.MethodGet, "/auth/cloud-handoff?token="+url.QueryEscape(token), nil)
rec := httptest.NewRecorder()
HandleCloudHandoff(configDir).ServeHTTP(rec, req)
if rec.Code != http.StatusTemporaryRedirect {
t.Fatalf("status=%d, want %d: %s", rec.Code, http.StatusTemporaryRedirect, rec.Body.String())
}
if got := rec.Header().Get("Location"); got != "/" {
t.Fatalf("redirect=%q, want %q", got, "/")
}
org, err := mtp.LoadOrganization(tenantID)
if err != nil {
t.Fatalf("load organization: %v", err)
}
if org.OwnerUserID != "legacy-owner@example.com" {
t.Fatalf("ownerUserID=%q, want %q", org.OwnerUserID, "legacy-owner@example.com")
}
if got := org.GetMemberRole("user-direct-contract"); got != models.OrgRoleOwner {
t.Fatalf("stable member role=%q, want %q", got, models.OrgRoleOwner)
}
if got := org.Members[1].Email; got != "courtmanr@gmail.com" {
t.Fatalf("canonicalized member email=%q, want %q", got, "courtmanr@gmail.com")
}
}
func TestContract_APITokenDeleteRejectsScopeEscalation(t *testing.T) {
router := &Router{
config: &config.Config{
APITokens: []config.APITokenRecord{
{
ID: "broad-token",
Name: "broad",
Hash: "hash-broad",
CreatedAt: time.Now().Add(-time.Hour),
Scopes: []string{config.ScopeWildcard},
OrgID: "default",
},
},
},
}
caller, err := config.NewAPITokenRecord(
"limited-caller-token-123.12345678",
"limited",
[]string{config.ScopeSettingsWrite},
)
if err != nil {
t.Fatalf("NewAPITokenRecord: %v", err)
}
req := httptest.NewRequest(http.MethodDelete, "/api/security/tokens/broad-token", nil)
req = req.WithContext(authpkg.WithAPIToken(req.Context(), caller))
rec := httptest.NewRecorder()
router.handleDeleteAPIToken(rec, req)
if rec.Code != http.StatusForbidden {
t.Fatalf("status = %d, want %d", rec.Code, http.StatusForbidden)
}
if !strings.Contains(rec.Body.String(), `Cannot delete token with scope "*"`) {
t.Fatalf("expected delete scope-escalation contract message, got %q", rec.Body.String())
}
if len(router.config.APITokens) != 1 || router.config.APITokens[0].ID != "broad-token" {
t.Fatalf("expected broader token to remain configured, got %+v", router.config.APITokens)
}
}
func TestContract_OrganizationInvitationTransportJSONSnapshot(t *testing.T) {
t.Setenv("PULSE_DEV", "true")
defer SetMultiTenantEnabled(false)
SetMultiTenantEnabled(true)
persistence := config.NewMultiTenantPersistence(t.TempDir())
h := NewOrgHandlers(persistence, nil)
createReq := withUser(
httptest.NewRequest(http.MethodPost, "/api/orgs", bytes.NewBufferString(`{"id":"acme","displayName":"Acme"}`)),
"alice",
)
createRec := httptest.NewRecorder()
h.HandleCreateOrg(createRec, createReq)
if createRec.Code != http.StatusCreated {
t.Fatalf("create org: status=%d body=%s", createRec.Code, createRec.Body.String())
}
inviteReq := withUser(
httptest.NewRequest(http.MethodPost, "/api/orgs/acme/members", bytes.NewBufferString(`{"userId":"bob","role":"viewer"}`)),
"alice",
)
inviteReq.SetPathValue("id", "acme")
inviteRec := httptest.NewRecorder()
h.HandleInviteMember(inviteRec, inviteReq)
if inviteRec.Code != http.StatusAccepted {
t.Fatalf("invite member: status=%d body=%s", inviteRec.Code, inviteRec.Body.String())
}
var invitePayload organizationAccessMutationResponse
if err := json.Unmarshal(inviteRec.Body.Bytes(), &invitePayload); err != nil {
t.Fatalf("decode invite payload: %v", err)
}
if invitePayload.Kind != "invitation" || invitePayload.Invitation == nil {
t.Fatalf("unexpected invite payload: %+v", invitePayload)
}
if invitePayload.Invitation.InvitedAt.IsZero() {
t.Fatalf("expected invite payload to include invitedAt")
}
inboxReq := withUser(httptest.NewRequest(http.MethodGet, "/api/org-invitations", nil), "bob")
inboxRec := httptest.NewRecorder()
h.HandleListMyInvitations(inboxRec, inboxReq)
if inboxRec.Code != http.StatusOK {
t.Fatalf("list my invitations: status=%d body=%s", inboxRec.Code, inboxRec.Body.String())
}
var inboxPayload []organizationUserInvitationResponse
if err := json.Unmarshal(inboxRec.Body.Bytes(), &inboxPayload); err != nil {
t.Fatalf("decode invitation inbox: %v", err)
}
if len(inboxPayload) != 1 {
t.Fatalf("expected 1 invitation, got %d", len(inboxPayload))
}
if inboxPayload[0].InvitedAt.IsZero() {
t.Fatalf("expected inbox payload to include invitedAt")
}
acceptReq := withUser(httptest.NewRequest(http.MethodPost, "/api/org-invitations/acme/accept", nil), "bob")
acceptReq.SetPathValue("id", "acme")
acceptRec := httptest.NewRecorder()
h.HandleAcceptMyInvitation(acceptRec, acceptReq)
if acceptRec.Code != http.StatusOK {
t.Fatalf("accept invitation: status=%d body=%s", acceptRec.Code, acceptRec.Body.String())
}
var acceptPayload organizationAccessMutationResponse
if err := json.Unmarshal(acceptRec.Body.Bytes(), &acceptPayload); err != nil {
t.Fatalf("decode accept payload: %v", err)
}
if acceptPayload.Kind != "member" || acceptPayload.Member == nil {
t.Fatalf("unexpected accept payload: %+v", acceptPayload)
}
if acceptPayload.Member.AddedAt.IsZero() {
t.Fatalf("expected accept payload to include addedAt")
}
got, err := json.Marshal(struct {
Invite struct {
Kind string `json:"kind"`
Invitation struct {
UserID string `json:"userId"`
Role string `json:"role"`
InvitedAt string `json:"invitedAt"`
InvitedBy string `json:"invitedBy"`
} `json:"invitation"`
} `json:"invite"`
Inbox []struct {
UserID string `json:"userId"`
Role string `json:"role"`
InvitedAt string `json:"invitedAt"`
InvitedBy string `json:"invitedBy"`
OrgID string `json:"orgId"`
OrgDisplayName string `json:"orgDisplayName"`
} `json:"inbox"`
Accept struct {
Kind string `json:"kind"`
Member struct {
UserID string `json:"userId"`
Role string `json:"role"`
AddedAt string `json:"addedAt"`
AddedBy string `json:"addedBy"`
} `json:"member"`
} `json:"accept"`
}{
Invite: struct {
Kind string `json:"kind"`
Invitation struct {
UserID string `json:"userId"`
Role string `json:"role"`
InvitedAt string `json:"invitedAt"`
InvitedBy string `json:"invitedBy"`
} `json:"invitation"`
}{
Kind: invitePayload.Kind,
Invitation: struct {
UserID string `json:"userId"`
Role string `json:"role"`
InvitedAt string `json:"invitedAt"`
InvitedBy string `json:"invitedBy"`
}{
UserID: invitePayload.Invitation.UserID,
Role: string(invitePayload.Invitation.Role),
InvitedAt: "placeholder",
InvitedBy: invitePayload.Invitation.InvitedBy,
},
},
Inbox: []struct {
UserID string `json:"userId"`
Role string `json:"role"`
InvitedAt string `json:"invitedAt"`
InvitedBy string `json:"invitedBy"`
OrgID string `json:"orgId"`
OrgDisplayName string `json:"orgDisplayName"`
}{
{
UserID: inboxPayload[0].UserID,
Role: string(inboxPayload[0].Role),
InvitedAt: "placeholder",
InvitedBy: inboxPayload[0].InvitedBy,
OrgID: inboxPayload[0].OrgID,
OrgDisplayName: inboxPayload[0].OrgDisplayName,
},
},
Accept: struct {
Kind string `json:"kind"`
Member struct {
UserID string `json:"userId"`
Role string `json:"role"`
AddedAt string `json:"addedAt"`
AddedBy string `json:"addedBy"`
} `json:"member"`
}{
Kind: acceptPayload.Kind,
Member: struct {
UserID string `json:"userId"`
Role string `json:"role"`
AddedAt string `json:"addedAt"`
AddedBy string `json:"addedBy"`
}{
UserID: acceptPayload.Member.UserID,
Role: string(acceptPayload.Member.Role),
AddedAt: "placeholder",
AddedBy: acceptPayload.Member.AddedBy,
},
},
})
if err != nil {
t.Fatalf("marshal normalized invitation transport snapshot: %v", err)
}
const want = `{
"invite":{
"kind":"invitation",
"invitation":{"userId":"bob","role":"viewer","invitedAt":"placeholder","invitedBy":"alice"}
},
"inbox":[
{"userId":"bob","role":"viewer","invitedAt":"placeholder","invitedBy":"alice","orgId":"acme","orgDisplayName":"Acme"}
],
"accept":{
"kind":"member",
"member":{"userId":"bob","role":"viewer","addedAt":"placeholder","addedBy":"alice"}
}
}`
assertJSONSnapshot(t, got, want)
}
func TestContract_OwnerTransferRequiresFreshSession(t *testing.T) {
t.Setenv("PULSE_DEV", "true")
defer SetMultiTenantEnabled(false)
SetMultiTenantEnabled(true)
configDir := t.TempDir()
resetSessionStoreForTests()
t.Cleanup(resetSessionStoreForTests)
InitSessionStore(configDir)
persistence := config.NewMultiTenantPersistence(t.TempDir())
h := NewOrgHandlers(persistence, nil)
createReq := withUser(
httptest.NewRequest(http.MethodPost, "/api/orgs", bytes.NewBufferString(`{"id":"acme","displayName":"Acme"}`)),
"alice",
)
createRec := httptest.NewRecorder()
h.HandleCreateOrg(createRec, createReq)
if createRec.Code != http.StatusCreated {
t.Fatalf("create org: status=%d body=%s", createRec.Code, createRec.Body.String())
}
inviteMemberForTest(t, h, "acme", "alice", "bob", "viewer", http.StatusAccepted)
acceptInvitationForTest(t, h, "acme", "bob")
sessionToken := generateSessionToken()
store := GetSessionStore()
store.CreateSession(sessionToken, time.Hour, "browser", "127.0.0.1", "alice")
store.mu.Lock()
store.sessions[sessionHash(sessionToken)].CreatedAt = time.Now().Add(-privilegedBrowserSessionMaxAge - time.Minute)
store.mu.Unlock()
transferReq := withUser(
httptest.NewRequest(http.MethodPost, "/api/orgs/acme/members", bytes.NewBufferString(`{"userId":"bob","role":"owner"}`)),
"alice",
)
transferReq.AddCookie(&http.Cookie{Name: cookieNameSession, Value: sessionToken})
transferReq.SetPathValue("id", "acme")
transferRec := httptest.NewRecorder()
h.HandleInviteMember(transferRec, transferReq)
if transferRec.Code != http.StatusUnauthorized {
t.Fatalf("status=%d, want %d: %s", transferRec.Code, http.StatusUnauthorized, transferRec.Body.String())
}
var payload APIError
if err := json.Unmarshal(transferRec.Body.Bytes(), &payload); err != nil {
t.Fatalf("decode error payload: %v", err)
}
if payload.Code != "fresh_session_required" {
t.Fatalf("code=%q, want fresh_session_required", payload.Code)
}
if payload.ErrorMessage != "Sign in again to transfer ownership" {
t.Fatalf("error=%q, want %q", payload.ErrorMessage, "Sign in again to transfer ownership")
}
if payload.StatusCode != http.StatusUnauthorized {
t.Fatalf("status_code=%d, want %d", payload.StatusCode, http.StatusUnauthorized)
}
}
func TestContract_OnboardingQRResponseJSONSnapshot(t *testing.T) {
payload := onboardingQRResponse{
Schema: onboardingSchemaVersion,
InstanceURL: "https://pulse.example.test",
InstanceID: "relay_abc123",
Relay: onboardingRelayDetails{
Enabled: true,
URL: "wss://relay.example.test/ws/app",
IdentityFingerprint: "AA:BB:CC",
IdentityPublicKey: "base64-key",
},
AuthToken: "token-123",
DeepLink: "pulse://connect?schema=pulse-mobile-onboarding-v1&instance_url=https%3A%2F%2Fpulse.example.test&instance_id=relay_abc123&relay_url=wss%3A%2F%2Frelay.example.test%2Fws%2Fapp&auth_token=token-123&identity_fingerprint=AA%3ABB%3ACC&identity_public_key=base64-key",
}.normalizeCollections()
got, err := json.Marshal(payload)
if err != nil {
t.Fatalf("marshal onboarding qr response: %v", err)
}
const want = `{
"schema":"pulse-mobile-onboarding-v1",
"instance_url":"https://pulse.example.test",
"instance_id":"relay_abc123",
"relay":{"enabled":true,"url":"wss://relay.example.test/ws/app","identity_fingerprint":"AA:BB:CC","identity_public_key":"base64-key"},
"auth_token":"token-123",
"deep_link":"pulse://connect?schema=pulse-mobile-onboarding-v1\u0026instance_url=https%3A%2F%2Fpulse.example.test\u0026instance_id=relay_abc123\u0026relay_url=wss%3A%2F%2Frelay.example.test%2Fws%2Fapp\u0026auth_token=token-123\u0026identity_fingerprint=AA%3ABB%3ACC\u0026identity_public_key=base64-key",
"diagnostics":[]
}`
assertJSONSnapshot(t, got, want)
}
func TestContract_OnboardingNotReadyResponseJSONSnapshot(t *testing.T) {
payload := onboardingNotReadyResponse{
Code: onboardingNotReadyCode,
Error: onboardingNotReadyMessage,
Message: onboardingNotReadyMessage,
Diagnostics: []onboardingDiagnostic{
{
Code: "relay_registration_unavailable",
Severity: "error",
Field: "instance_id",
Message: "Remote Access is enabled, but this Pulse instance is not connected to the relay yet. Wait for the status to show Connected before generating a mobile pairing code.",
},
},
}
got, err := json.Marshal(payload)
if err != nil {
t.Fatalf("marshal onboarding not-ready response: %v", err)
}
const want = `{
"code":"onboarding_not_ready",
"error":"Pulse Mobile pairing is not ready yet.",
"message":"Pulse Mobile pairing is not ready yet.",
"diagnostics":[{"code":"relay_registration_unavailable","severity":"error","message":"Remote Access is enabled, but this Pulse instance is not connected to the relay yet. Wait for the status to show Connected before generating a mobile pairing code.","field":"instance_id"}]
}`
assertJSONSnapshot(t, got, want)
}
func TestContract_HostedRelayConfigResponseJSONSnapshot(t *testing.T) {
router, _, instanceHost := newHostedRelayRuntimeTestRouter(t)
req := httptest.NewRequest(http.MethodGet, "/api/settings/relay", nil)
rec := httptest.NewRecorder()
router.handleGetRelayConfig(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("status=%d, want %d: %s", rec.Code, http.StatusOK, rec.Body.String())
}
cfg, err := router.loadRelayConfigForRuntime(context.Background())
if err != nil {
t.Fatalf("loadRelayConfigForRuntime() error = %v", err)
}
body := rec.Body.String()
if strings.Contains(body, instanceHost) {
t.Fatalf("relay config response leaked hosted instance secret %q: %s", instanceHost, body)
}
if strings.Contains(body, cfg.IdentityPrivateKey) {
t.Fatalf("relay config response leaked identity private key: %s", body)
}
want := fmt.Sprintf(`{
"enabled":true,
"server_url":"%s",
"identity_public_key":"%s",
"identity_fingerprint":"%s"
}`, relay.DefaultServerURL, cfg.IdentityPublicKey, cfg.IdentityFingerprint)
assertJSONSnapshot(t, rec.Body.Bytes(), want)
}
func TestContract_DiagnosticsInfoJSONSnapshot(t *testing.T) {
payload := EmptyDiagnosticsInfo()
got, err := json.Marshal(payload.NormalizeCollections())
if err != nil {
t.Fatalf("marshal diagnostics info: %v", err)
}
const want = `{
"version":"",
"runtime":"",
"uptime":0,
"nodes":[],
"pbs":[],
"system":{"os":"","arch":"","goVersion":"","numCPU":0,"numGoroutine":0,"memoryMB":0},
"errors":[],
"nodeSnapshots":[],
"guestSnapshots":[],
"memorySources":[],
"memorySourceBreakdown":[]
}`
assertJSONSnapshot(t, got, want)
}
func TestContract_UpdatePlanManualFallbackJSONSnapshot(t *testing.T) {
payload := updates.UpdatePlan{
CanAutoUpdate: false,
RequiresRoot: false,
RollbackSupport: true,
EstimatedTime: "5-10 minutes",
Instructions: []string{
"Check out or build Pulse 6.0.0-rc.1 in your development workspace.",
"Stop the current development instance.",
"Restart Pulse with the rebuilt binary or release artifact against the existing data directory.",
},
Prerequisites: []string{
"A local development workspace for Pulse",
"Build tooling for the target version",
"A backup of the active data directory before replacing the binary",
},
}
got, err := json.Marshal(payload)
if err != nil {
t.Fatalf("marshal update plan: %v", err)
}
const want = `{
"canAutoUpdate":false,
"instructions":["Check out or build Pulse 6.0.0-rc.1 in your development workspace.","Stop the current development instance.","Restart Pulse with the rebuilt binary or release artifact against the existing data directory."],
"prerequisites":["A local development workspace for Pulse","Build tooling for the target version","A backup of the active data directory before replacing the binary"],
"estimatedTime":"5-10 minutes",
"requiresRoot":false,
"rollbackSupport":true
}`
assertJSONSnapshot(t, got, want)
}
func TestContract_EmptyUpdatePlanJSONSnapshot(t *testing.T) {
payload := updates.EmptyUpdatePlan()
got, err := json.Marshal(payload)
if err != nil {
t.Fatalf("marshal empty update plan: %v", err)
}
const want = `{
"canAutoUpdate":false,
"instructions":[],
"prerequisites":[],
"requiresRoot":false,
"rollbackSupport":false
}`
assertJSONSnapshot(t, got, want)
}
func TestContract_APITokenDTOJSONSnapshot(t *testing.T) {
now := time.Date(2026, 2, 8, 13, 14, 15, 0, time.UTC)
lastUsed := now.Add(30 * time.Minute)
expires := now.Add(24 * time.Hour)
payload := apiTokenDTO{
ID: "token-1",
Name: "Deploy token",
Prefix: "pulse_",
Suffix: "1234",
CreatedAt: now,
LastUsedAt: &lastUsed,
ExpiresAt: &expires,
Scopes: []string{"monitoring:read", "settings:write"},
OwnerUserID: "owner@example.com",
}
got, err := json.Marshal(payload)
if err != nil {
t.Fatalf("marshal API token dto: %v", err)
}
const want = `{
"id":"token-1",
"name":"Deploy token",
"prefix":"pulse_",
"suffix":"1234",
"createdAt":"2026-02-08T13:14:15Z",
"lastUsedAt":"2026-02-08T13:44:15Z",
"expiresAt":"2026-02-09T13:14:15Z",
"scopes":["monitoring:read","settings:write"],
"ownerUserId":"owner@example.com"
}`
assertJSONSnapshot(t, got, want)
}
func TestContract_APITokenScopeAliasNormalization(t *testing.T) {
raw := []string{"host-agent:report", "host-agent:config:read", "host-agent:manage", "host-agent:enroll"}
got, err := normalizeRequestedScopes(&raw)
if err != nil {
t.Fatalf("normalize requested scopes: %v", err)
}
want := []string{
config.ScopeAgentConfigRead,
config.ScopeAgentEnroll,
config.ScopeAgentManage,
config.ScopeAgentReport,
}
if !reflect.DeepEqual(got, want) {
t.Fatalf("normalized scopes = %#v, want %#v", got, want)
}
for _, legacy := range raw {
if strings.HasPrefix(legacy, "agent:") {
t.Fatalf("expected legacy alias input, got canonical scope %q", legacy)
}
}
}
func TestContract_HostedSubscriptionRequiredErrorJSONSnapshot(t *testing.T) {
rec := httptest.NewRecorder()
writeHostedSubscriptionRequiredError(rec)
if rec.Code != http.StatusPaymentRequired {
t.Fatalf("status = %d, want %d", rec.Code, http.StatusPaymentRequired)
}
const want = `{
"error":"subscription_required",
"message":"Your Cloud subscription is not active. Please check your billing status."
}`
assertJSONSnapshot(t, rec.Body.Bytes(), want)
}
func TestContract_AgentBinaryReleaseAssetURL(t *testing.T) {
router := &Router{serverVersion: "v6.0.0-rc.1"}
got, err := router.agentBinaryReleaseAssetURL("linux-arm64")
if err != nil {
t.Fatalf("agent binary release asset URL: %v", err)
}
const want = "https://github.com/rcourtman/Pulse/releases/download/v6.0.0-rc.1/pulse-agent-linux-arm64"
if got != want {
t.Fatalf("agent binary release asset URL = %q, want %q", got, want)
}
}
func TestContract_AgentBinaryReleaseAssetURLRejectsDevPrereleaseBuild(t *testing.T) {
router := &Router{serverVersion: "v6.0.0-dev"}
if _, err := router.agentBinaryReleaseAssetURL("linux-arm64"); err == nil {
t.Fatalf("expected dev prerelease build to reject release asset lookup")
}
}
func TestContract_ProxmoxInstallCommandIncludesInsecureForPlainHTTP(t *testing.T) {
got := buildProxmoxAgentInstallCommand(agentInstallCommandOptions{
BaseURL: "http://pulse.example.com:7655/",
Token: "token-123",
InstallType: "pve",
IncludeInstallType: true,
})
if !strings.Contains(got, "--url "+posixShellQuote("http://pulse.example.com:7655")) {
t.Fatalf("install command missing canonical base URL: %s", got)
}
if !strings.Contains(got, "--insecure") {
t.Fatalf("install command missing insecure flag for plain HTTP Pulse URL: %s", got)
}
if !strings.Contains(got, `--token-file "$token_file"`) {
t.Fatalf("install command missing token-file transport: %s", got)
}
}
func TestContract_ProxmoxInstallCommandUsesPrivilegeEscalationWrapper(t *testing.T) {
got := buildProxmoxAgentInstallCommand(agentInstallCommandOptions{
BaseURL: "https://pulse.example.com/",
Token: "token-123",
InstallType: "pve",
IncludeInstallType: true,
})
if !strings.Contains(got, `| { if [ "$(id -u)" -eq 0 ]; then bash -s --`) {
t.Fatalf("install command missing root-or-sudo wrapper: %s", got)
}
if !strings.Contains(got, `sudo bash -s --`) {
t.Fatalf("install command missing sudo fallback: %s", got)
}
if strings.Contains(got, "| bash -s -- --url") {
t.Fatalf("install command preserved raw bash pipe instead of governed wrapper: %s", got)
}
if !strings.Contains(got, `rm -f "$token_file"`) {
t.Fatalf("install command missing ephemeral token cleanup: %s", got)
}
}
func TestContract_OptionalAuthProxmoxInstallCommandOmitsToken(t *testing.T) {
got := buildProxmoxAgentInstallCommand(agentInstallCommandOptions{
BaseURL: "https://pulse.example.com/",
Token: "",
InstallType: "pve",
IncludeInstallType: true,
})
if strings.Contains(got, "--token") {
t.Fatalf("optional-auth install command preserved token flag: %s", got)
}
if !strings.Contains(got, "--url "+posixShellQuote("https://pulse.example.com")) {
t.Fatalf("optional-auth install command missing canonical base URL: %s", got)
}
}
func TestContract_ProxmoxInstallCommandNormalizesTrailingSlashBaseURL(t *testing.T) {
got := buildProxmoxAgentInstallCommand(agentInstallCommandOptions{
BaseURL: "https://pulse.example.com/base///",
Token: "token-123",
InstallType: "pbs",
IncludeInstallType: true,
})
if !strings.Contains(got, posixShellQuote("https://pulse.example.com/base/install.sh")) {
t.Fatalf("install command missing normalized install script URL: %s", got)
}
if !strings.Contains(got, "--url "+posixShellQuote("https://pulse.example.com/base")) {
t.Fatalf("install command missing normalized base URL: %s", got)
}
if !strings.Contains(got, `--token-file "$token_file"`) {
t.Fatalf("install command missing token-file transport: %s", got)
}
if strings.Contains(got, "//install.sh") {
t.Fatalf("install command preserved double-slash install path: %s", got)
}
}
func TestContract_DownloadUnifiedAgentReleaseAssetIncludesSignatureHeader(t *testing.T) {
router, tempDir := setupUnifiedAgentRouter(t)
router.serverVersion = "v6.0.0"
binContent := validTestUnifiedAgentBinary("linux-amd64")
binPath := filepath.Join(tempDir, "bin", "pulse-agent-linux-amd64")
if err := os.WriteFile(binPath, binContent, 0o755); err != nil {
t.Fatalf("write binary: %v", err)
}
if err := os.WriteFile(binPath+".sig", []byte("signed-agent"), 0o644); err != nil {
t.Fatalf("write signature: %v", err)
}
if err := os.WriteFile(binPath+".sshsig", []byte("signed-agent-ssh"), 0o644); err != nil {
t.Fatalf("write ssh signature: %v", err)
}
req := httptest.NewRequest(http.MethodGet, "/api/install/agent?arch=linux-amd64", nil)
w := httptest.NewRecorder()
router.handleDownloadUnifiedAgent(w, req)
if got := w.Header().Get(signatureHeaderName); got != "signed-agent" {
t.Fatalf("agent download signature header = %q, want %q", got, "signed-agent")
}
if got := w.Header().Get(sshSignatureHeaderName); got != encodedTestSSHSignature("signed-agent-ssh") {
t.Fatalf("agent download SSH signature header = %q, want %q", got, encodedTestSSHSignature("signed-agent-ssh"))
}
}
func TestContract_DownloadInstallScriptReleaseAssetIncludesSignatureHeader(t *testing.T) {
router, tempDir := setupUnifiedAgentRouter(t)
router.serverVersion = "v6.0.0"
scriptPath := filepath.Join(tempDir, "scripts", "install.sh")
if err := os.WriteFile(scriptPath, []byte("#!/bin/sh\necho ok\n"), 0o755); err != nil {
t.Fatalf("write install script: %v", err)
}
if err := os.WriteFile(scriptPath+".sig", []byte("signed-install-script"), 0o644); err != nil {
t.Fatalf("write install signature: %v", err)
}
if err := os.WriteFile(scriptPath+".sshsig", []byte("signed-install-script-ssh"), 0o644); err != nil {
t.Fatalf("write install SSH signature: %v", err)
}
req := httptest.NewRequest(http.MethodGet, "/install.sh", nil)
w := httptest.NewRecorder()
router.handleDownloadUnifiedInstallScript(w, req)
if got := w.Header().Get(signatureHeaderName); got != "signed-install-script" {
t.Fatalf("install script signature header = %q, want %q", got, "signed-install-script")
}
if got := w.Header().Get(sshSignatureHeaderName); got != encodedTestSSHSignature("signed-install-script-ssh") {
t.Fatalf("install script SSH signature header = %q, want %q", got, encodedTestSSHSignature("signed-install-script-ssh"))
}
}
func TestContract_SystemSettingsResponseJSONSnapshot(t *testing.T) {
payload := EmptySystemSettingsResponse()
payload.SystemSettings = config.SystemSettings{
PVEPollingInterval: 30,
PBSPollingInterval: 60,
PMGPollingInterval: 60,
BackupPollingInterval: 3600,
UpdateChannel: "rc",
AutoUpdateEnabled: false,
AutoUpdateCheckInterval: 24,
AutoUpdateTime: "03:00",
DiscoveryEnabled: true,
DiscoverySubnet: "10.0.0.0/24",
DiscoveryConfig: config.DefaultDiscoveryConfig(),
Theme: "dark",
TemperatureMonitoringEnabled: true,
DisableDockerUpdateActions: true,
}
payload.EnvOverrides = map[string]bool{
"PULSE_TELEMETRY": true,
}
got, err := json.Marshal(payload)
if err != nil {
t.Fatalf("marshal system settings response: %v", err)
}
const want = `{
"pvePollingInterval":30,
"pbsPollingInterval":60,
"pmgPollingInterval":60,
"backupPollingInterval":3600,
"updateChannel":"rc",
"autoUpdateEnabled":false,
"autoUpdateCheckInterval":24,
"autoUpdateTime":"03:00",
"discoveryEnabled":true,
"discoverySubnet":"10.0.0.0/24",
"discoveryConfig":{
"environment_override":"auto",
"subnet_blocklist":["169.254.0.0/16"],
"max_hosts_per_scan":1024,
"max_concurrent":50,
"enable_reverse_dns":true,
"scan_gateways":true,
"dial_timeout_ms":1000,
"http_timeout_ms":2000
},
"theme":"dark",
"fullWidthMode":false,
"allowEmbedding":false,
"temperatureMonitoringEnabled":true,
"hideLocalLogin":false,
"disableDockerUpdateActions":true,
"envOverrides":{"PULSE_TELEMETRY":true}
}`
assertJSONSnapshot(t, got, want)
}
func TestContract_CachedDiscoveryResponseJSONSnapshot(t *testing.T) {
response := map[string]interface{}{
"servers": []map[string]interface{}{
{
"ip": "10.0.0.1",
"port": 8006,
"type": "pve",
},
},
"errors": []string{
"Docker bridge network [10.0.0.2:8007]: request timed out",
},
"structured_errors": []map[string]interface{}{
{
"ip": "10.0.0.2",
"port": 8007,
"phase": "docker_bridge_network",
"error_type": "timeout",
"message": "request timed out",
"timestamp": "2023-11-14T22:13:20Z",
},
},
"environment": nil,
"cached": true,
"updated": int64(1700000010),
"age": float64(0),
}
got, err := json.Marshal(response)
if err != nil {
t.Fatalf("marshal cached discovery response: %v", err)
}
const want = `{
"age":0,
"cached":true,
"environment":null,
"errors":["Docker bridge network [10.0.0.2:8007]: request timed out"],
"servers":[{"ip":"10.0.0.1","port":8006,"type":"pve"}],
"structured_errors":[{"error_type":"timeout","ip":"10.0.0.2","message":"request timed out","phase":"docker_bridge_network","port":8007,"timestamp":"2023-11-14T22:13:20Z"}],
"updated":1700000010
}`
var wantValue interface{}
if err := json.Unmarshal([]byte(want), &wantValue); err != nil {
t.Fatalf("unmarshal wanted discovery response: %v", err)
}
var gotValue interface{}
if err := json.Unmarshal(got, &gotValue); err != nil {
t.Fatalf("unmarshal got discovery response: %v", err)
}
if !reflect.DeepEqual(gotValue, wantValue) {
t.Fatalf("cached discovery response mismatch\nwant: %s\ngot: %s", want, string(got))
}
}
func TestContract_DiscoveryCommandScanRoutesRequireSettingsWrite(t *testing.T) {
monitoringToken := "discovery-command-monitoring-token-123.12345678"
settingsToken := "discovery-command-settings-token-123.12345678"
monitoringRecord := newTokenRecord(t, monitoringToken, []string{config.ScopeMonitoringWrite}, nil)
settingsRecord := newTokenRecord(t, settingsToken, []string{config.ScopeSettingsWrite}, nil)
cfg := newTestConfigWithTokens(t, monitoringRecord, settingsRecord)
router := NewRouter(cfg, nil, nil, nil, nil, "1.0.0")
paths := []string{
"/api/discovery/run",
"/api/discovery/agent/host-1/resource-1",
"/api/discovery/vm/host-1/resource-1",
}
for _, path := range paths {
req := httptest.NewRequest(http.MethodPost, path, strings.NewReader(`{}`))
req.Header.Set("X-API-Token", monitoringToken)
rec := httptest.NewRecorder()
router.Handler().ServeHTTP(rec, req)
if rec.Code != http.StatusForbidden {
t.Fatalf("expected monitoring:write token to be forbidden for command-scan route %s, got %d", path, rec.Code)
}
if !strings.Contains(rec.Body.String(), config.ScopeSettingsWrite) {
t.Fatalf("expected command-scan route %s to require %q, got %q", path, config.ScopeSettingsWrite, rec.Body.String())
}
req = httptest.NewRequest(http.MethodPost, path, strings.NewReader(`{}`))
req.Header.Set("X-API-Token", settingsToken)
rec = httptest.NewRecorder()
router.Handler().ServeHTTP(rec, req)
if rec.Code == http.StatusForbidden && strings.Contains(rec.Body.String(), config.ScopeSettingsWrite) {
t.Fatalf("expected settings:write token to pass route scope for command-scan route %s, got %d: %s", path, rec.Code, rec.Body.String())
}
}
}
func TestContract_WorkloadDiscoveryTriggerLeavesHostnameForServiceResolution(t *testing.T) {
source, err := os.ReadFile(filepath.Clean("discovery_handlers.go"))
if err != nil {
t.Fatalf("read discovery handlers: %v", err)
}
text := string(source)
for _, required := range []string{
"Hostname: reqBody.Hostname",
"resourceType == servicediscovery.ResourceTypeAgent",
"req.Hostname = targetID",
"Workloads should resolve their own display name from state",
} {
if !strings.Contains(text, required) {
t.Fatalf("discovery trigger lost workload hostname contract marker %q", required)
}
}
if strings.Contains(text, "if req.Hostname == \"\" {\n\t\treq.Hostname = targetID") {
t.Fatal("discovery trigger must not use the route target as a generic workload hostname fallback")
}
}
func TestContract_AutoRegisterRequestJSONSnapshot(t *testing.T) {
payload := AutoRegisterRequest{
Type: "pve",
Host: "https://pve.local:8006",
TokenID: "pulse-monitor@pve!pulse-homelab",
TokenValue: "secret-token",
ServerName: "pve-node-1",
AuthToken: "setup-token-123",
Source: "agent",
}
got, err := json.Marshal(payload)
if err != nil {
t.Fatalf("marshal auto-register request: %v", err)
}
const want = `{
"type":"pve",
"host":"https://pve.local:8006",
"tokenId":"pulse-monitor@pve!pulse-homelab",
"tokenValue":"secret-token",
"serverName":"pve-node-1",
"authToken":"setup-token-123",
"source":"agent"
}`
assertJSONSnapshot(t, got, want)
}
func TestContract_AutoRegisterScriptRequestJSONSnapshot(t *testing.T) {
payload := AutoRegisterRequest{
Type: "pve",
Host: "https://pve.local:8006",
TokenID: "pulse-monitor@pve!pulse-homelab",
TokenValue: "secret-token",
ServerName: "pve-node-1",
AuthToken: "setup-token-123",
Source: "script",
}
got, err := json.Marshal(payload)
if err != nil {
t.Fatalf("marshal script auto-register request: %v", err)
}
const want = `{
"type":"pve",
"host":"https://pve.local:8006",
"tokenId":"pulse-monitor@pve!pulse-homelab",
"tokenValue":"secret-token",
"serverName":"pve-node-1",
"authToken":"setup-token-123",
"source":"script"
}`
assertJSONSnapshot(t, got, want)
}
func TestContract_AutoRegisterCheckRequestJSONSnapshot(t *testing.T) {
payload := AutoRegisterRequest{
Type: "pve",
Host: "https://pve.local:8006",
CandidateHosts: []string{"https://pve.local:8006", "https://10.0.0.5:8006"},
ServerName: "pve-node-1",
AuthToken: "setup-token-123",
Source: "agent",
CheckRegistration: true,
}
got, err := json.Marshal(payload)
if err != nil {
t.Fatalf("marshal auto-register check request: %v", err)
}
const want = `{
"type":"pve",
"host":"https://pve.local:8006",
"candidateHosts":["https://pve.local:8006","https://10.0.0.5:8006"],
"serverName":"pve-node-1",
"authToken":"setup-token-123",
"source":"agent",
"checkRegistration":true
}`
assertJSONSnapshot(t, got, want)
}
func TestContract_AutoUnregisterRequestJSONSnapshot(t *testing.T) {
payload := AutoUnregisterRequest{
Type: "pve",
Host: "https://pve.local:8006",
TokenID: "pulse-monitor@pve!pulse-homelab",
ServerName: "pve-node-1",
AuthToken: "setup-token-123",
Source: "script",
}
got, err := json.Marshal(payload)
if err != nil {
t.Fatalf("marshal auto-unregister request: %v", err)
}
const want = `{
"type":"pve",
"host":"https://pve.local:8006",
"tokenId":"pulse-monitor@pve!pulse-homelab",
"serverName":"pve-node-1",
"authToken":"setup-token-123",
"source":"script"
}`
assertJSONSnapshot(t, got, want)
}
func TestContract_AutoRegisterScriptRequestRequiresExplicitSourceMarker(t *testing.T) {
payload := AutoRegisterRequest{
Type: "pve",
Host: "https://pve.local:8006",
TokenID: "pulse-monitor@pve!pulse-homelab",
TokenValue: "secret-token",
ServerName: "pve-node-1",
AuthToken: "setup-token-123",
Source: "script",
}
got, err := json.Marshal(payload)
if err != nil {
t.Fatalf("marshal script auto-register request: %v", err)
}
var decoded map[string]any
if err := json.Unmarshal(got, &decoded); err != nil {
t.Fatalf("decode script auto-register request: %v", err)
}
if decoded["source"] != "script" {
t.Fatalf("source = %#v, want explicit script marker", decoded["source"])
}
}
func TestContract_CanonicalAutoRegisterRequiresExplicitServerName(t *testing.T) {
payload := AutoRegisterRequest{
Type: "pve",
Host: "https://pve.local:8006",
TokenID: "pulse-monitor@pve!pulse-homelab",
TokenValue: "secret-token",
AuthToken: "setup-token-123",
Source: "script",
}
got, err := json.Marshal(payload)
if err != nil {
t.Fatalf("marshal serverName-free auto-register request: %v", err)
}
var decoded map[string]any
if err := json.Unmarshal(got, &decoded); err != nil {
t.Fatalf("decode serverName-free auto-register request: %v", err)
}
if _, ok := decoded["serverName"]; ok {
t.Fatalf("serverName = %#v, want omitted when caller does not send it", decoded["serverName"])
}
}
func TestContract_CanonicalAutoRegisterSetupTokenAuthFailureText(t *testing.T) {
tempDir := t.TempDir()
t.Setenv("PULSE_DATA_DIR", tempDir)
cfg := &config.Config{
DataPath: tempDir,
ConfigPath: tempDir,
}
handler := newTestConfigHandlers(t, cfg)
requestBody := AutoRegisterRequest{
Type: "pve",
Host: "https://pve.local:8006",
TokenID: "pulse-monitor@pve!pulse-homelab",
TokenValue: "secret-token",
ServerName: "pve-node-1",
Source: "script",
}
missingAuthJSON, err := json.Marshal(requestBody)
if err != nil {
t.Fatalf("marshal missing-auth auto-register request: %v", err)
}
missingReq := httptest.NewRequest(http.MethodPost, "/api/auto-register", bytes.NewReader(missingAuthJSON))
missingRec := httptest.NewRecorder()
handler.HandleAutoRegister(missingRec, missingReq)
if missingRec.Code != http.StatusUnauthorized {
t.Fatalf("missing-auth status = %d, want 401", missingRec.Code)
}
if got := missingRec.Body.String(); got != "Pulse setup token required\n" {
t.Fatalf("missing-auth body = %q, want canonical missing-setup-token guidance", got)
}
requestBody.AuthToken = "invalid-setup-token"
invalidAuthJSON, err := json.Marshal(requestBody)
if err != nil {
t.Fatalf("marshal invalid-auth auto-register request: %v", err)
}
invalidReq := httptest.NewRequest(http.MethodPost, "/api/auto-register", bytes.NewReader(invalidAuthJSON))
invalidRec := httptest.NewRecorder()
handler.HandleAutoRegister(invalidRec, invalidReq)
if invalidRec.Code != http.StatusUnauthorized {
t.Fatalf("invalid-auth status = %d, want 401", invalidRec.Code)
}
if got := invalidRec.Body.String(); got != "Invalid or expired setup token\n" {
t.Fatalf("invalid-auth body = %q, want canonical setup-token auth failure text", got)
}
const validSetupToken = "setup-token-123"
tokenHash := authpkg.HashAPIToken(validSetupToken)
handler.codeMutex.Lock()
handler.setupTokens[tokenHash] = &SetupTokenRecord{
ExpiresAt: time.Now().Add(5 * time.Minute),
NodeType: "pve",
}
handler.codeMutex.Unlock()
requestBody.AuthToken = validSetupToken
requestBody.TokenValue = ""
mismatchedJSON, err := json.Marshal(requestBody)
if err != nil {
t.Fatalf("marshal mismatched-completion auto-register request: %v", err)
}
mismatchedReq := httptest.NewRequest(http.MethodPost, "/api/auto-register", bytes.NewReader(mismatchedJSON))
mismatchedRec := httptest.NewRecorder()
handler.HandleAutoRegister(mismatchedRec, mismatchedReq)
if mismatchedRec.Code != http.StatusBadRequest {
t.Fatalf("mismatched-completion status = %d, want 400", mismatchedRec.Code)
}
if got := mismatchedRec.Body.String(); got != "tokenId and tokenValue must be provided together\n" {
t.Fatalf("mismatched-completion body = %q, want canonical token-pair guidance", got)
}
}
func TestContract_BootstrapTokenPersistenceJSONSnapshot(t *testing.T) {
tempDir := t.TempDir()
token, created, path, err := loadOrCreateBootstrapToken(tempDir)
if err != nil {
t.Fatalf("loadOrCreateBootstrapToken() error = %v", err)
}
if !created {
t.Fatal("expected bootstrap token to be created")
}
data, err := os.ReadFile(path)
if err != nil {
t.Fatalf("read persisted bootstrap token: %v", err)
}
snapshot := string(data)
if !strings.Contains(snapshot, `"version":2`) {
t.Fatalf("bootstrap token snapshot missing version: %s", snapshot)
}
if !strings.Contains(snapshot, `"token_ciphertext":"`) {
t.Fatalf("bootstrap token snapshot missing ciphertext field: %s", snapshot)
}
if !strings.Contains(snapshot, `"token_hash":"`) {
t.Fatalf("bootstrap token snapshot missing token hash field: %s", snapshot)
}
if strings.Contains(snapshot, token) {
t.Fatalf("bootstrap token snapshot leaked raw token: %s", snapshot)
}
}
func TestContract_InitializeBootstrapTokenLogsPathNotSecret(t *testing.T) {
tempDir := t.TempDir()
cfg := &config.Config{DataPath: tempDir}
var logBuf bytes.Buffer
logger := zerolog.New(&logBuf)
router := &Router{config: cfg, eventLogger: &logger}
router.initializeBootstrapToken()
token, created, tokenPath, err := loadOrCreateBootstrapToken(tempDir)
if err != nil {
t.Fatalf("loadOrCreateBootstrapToken() error = %v", err)
}
if created {
t.Fatal("expected bootstrap token to already exist after initialization")
}
logOutput := logBuf.String()
if !strings.Contains(logOutput, tokenPath) {
t.Fatalf("bootstrap token log missing path %q in %q", tokenPath, logOutput)
}
if !strings.Contains(logOutput, "pulse bootstrap-token") {
t.Fatalf("bootstrap token log missing local reveal guidance: %q", logOutput)
}
if strings.Contains(logOutput, token) {
t.Fatalf("bootstrap token leaked into logs: %q", logOutput)
}
}
func TestContract_QuickSecuritySetupBootstrapRetrievalGuidance(t *testing.T) {
resetPersistentAuthStoresForTests()
t.Cleanup(resetPersistentAuthStoresForTests)
tempDir := t.TempDir()
cfg := &config.Config{
DataPath: tempDir,
ConfigPath: tempDir,
}
router := &Router{
config: cfg,
persistence: config.NewConfigPersistence(cfg.DataPath),
}
router.initializeBootstrapToken()
InitPersistentAuthStores(tempDir)
handler := handleQuickSecuritySetupFixed(router)
body := `{"username":"bootstrap","password":"StrongPass!1","apiToken":"` + strings.Repeat("aa", 32) + `"}`
req := httptest.NewRequest(http.MethodPost, "/api/security/quick-setup", strings.NewReader(body))
req.RemoteAddr = "127.0.0.1:54321"
rec := httptest.NewRecorder()
authLimiter.Reset("127.0.0.1")
handler(rec, req)
if rec.Code != http.StatusUnauthorized {
t.Fatalf("quick setup status = %d, want 401 (%s)", rec.Code, rec.Body.String())
}
if got := rec.Body.String(); !strings.Contains(got, "pulse bootstrap-token") {
t.Fatalf("quick setup guidance = %q, want pulse bootstrap-token retrieval guidance", got)
}
if got := rec.Body.String(); strings.Contains(got, ".bootstrap_token") {
t.Fatalf("quick setup guidance = %q, want no raw .bootstrap_token scraping guidance", got)
}
}
// A valid bootstrap token authorizes quick setup from any origin — the token
// is the security boundary, and it's only readable by callers with filesystem
// access to the Pulse data directory. The loopback-only path remains for the
// no-token fallback (legacy console flow).
func TestContract_QuickSecuritySetupValidBootstrapTokenAuthorizesAnyOrigin(t *testing.T) {
body := `{"username":"bootstrap","password":"StrongPass!1","apiToken":"` + strings.Repeat("aa", 32) + `"}`
newHandler := func(t *testing.T) (http.HandlerFunc, string) {
t.Helper()
tempDir := t.TempDir()
cfg := &config.Config{
DataPath: tempDir,
ConfigPath: tempDir,
}
router := &Router{
config: cfg,
persistence: config.NewConfigPersistence(cfg.DataPath),
}
router.initializeBootstrapToken()
InitPersistentAuthStores(tempDir)
token, _, _, err := loadOrCreateBootstrapToken(tempDir)
if err != nil {
t.Fatalf("loadOrCreateBootstrapToken: %v", err)
}
return handleQuickSecuritySetupFixed(router), token
}
t.Run("remote IP with valid bootstrap token succeeds", func(t *testing.T) {
handler, token := newHandler(t)
req := httptest.NewRequest(http.MethodPost, "/api/security/quick-setup", strings.NewReader(body))
req.RemoteAddr = "198.51.100.41:54321"
req.Header.Set(bootstrapTokenHeader, token)
rec := httptest.NewRecorder()
authLimiter.Reset("198.51.100.41")
handler(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("remote quick setup with bootstrap token status = %d, want 200 (%s)", rec.Code, rec.Body.String())
}
foundSessionCookie := false
for _, cookie := range rec.Result().Cookies() {
if cookie.Name == cookieNameSession || cookie.Name == cookieNameSessionSecure {
foundSessionCookie = true
break
}
}
if !foundSessionCookie {
t.Fatal("expected remote quick setup with bootstrap token to issue a session cookie")
}
})
t.Run("remote IP without bootstrap token is rejected with bootstrap guidance", func(t *testing.T) {
handler, _ := newHandler(t)
req := httptest.NewRequest(http.MethodPost, "/api/security/quick-setup", strings.NewReader(body))
req.RemoteAddr = "198.51.100.41:54321"
rec := httptest.NewRecorder()
authLimiter.Reset("198.51.100.41")
handler(rec, req)
if rec.Code != http.StatusForbidden {
t.Fatalf("remote quick setup without bootstrap token status = %d, want 403 (%s)", rec.Code, rec.Body.String())
}
if got := rec.Body.String(); !strings.Contains(strings.ToLower(got), "bootstrap") {
t.Fatalf("remote no-token rejection guidance = %q, want bootstrap-token message", got)
}
})
t.Run("loopback IP with valid bootstrap token succeeds", func(t *testing.T) {
handler, token := newHandler(t)
req := httptest.NewRequest(http.MethodPost, "/api/security/quick-setup", strings.NewReader(body))
req.RemoteAddr = "127.0.0.1:54321"
req.Header.Set(bootstrapTokenHeader, token)
rec := httptest.NewRecorder()
authLimiter.Reset("127.0.0.1")
handler(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("loopback quick setup status = %d, want 200 (%s)", rec.Code, rec.Body.String())
}
})
}
func TestContract_ResetFirstRunSecurityResponseJSONSnapshot(t *testing.T) {
t.Setenv("PULSE_DEV", "true")
t.Setenv("NODE_ENV", "")
record := newTokenRecord(t, "contract-reset-first-run-token-123.12345678", []string{config.ScopeSettingsWrite}, nil)
cfg := newTestConfigWithTokens(t, record)
cfg.AuthUser = "admin"
cfg.AuthPass = "hashed-password"
envPath, err := writeAuthEnvFile(cfg.ConfigPath, cfg.DataPath, []byte("PULSE_AUTH_USER='admin'\n"))
if err != nil {
t.Fatalf("writeAuthEnvFile: %v", err)
}
router := NewRouter(cfg, nil, nil, nil, nil, "1.0.0")
req := httptest.NewRequest(http.MethodPost, "/api/security/dev/reset-first-run", nil)
req.Header.Set("X-API-Token", "contract-reset-first-run-token-123.12345678")
rec := httptest.NewRecorder()
router.Handler().ServeHTTP(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("reset-first-run status = %d, want 200 (%s)", rec.Code, rec.Body.String())
}
var payload firstRunResetResponse
if err := json.Unmarshal(rec.Body.Bytes(), &payload); err != nil {
t.Fatalf("decode reset-first-run response: %v", err)
}
if strings.TrimSpace(payload.BootstrapToken) == "" {
t.Fatal("reset-first-run response missing bootstrapToken")
}
if got, want := payload.BootstrapTokenPath, filepath.Join(cfg.DataPath, bootstrapTokenFilename); got != want {
t.Fatalf("reset-first-run bootstrapTokenPath = %q, want %q", got, want)
}
if _, err := os.Stat(envPath); !os.IsNotExist(err) {
t.Fatalf("reset-first-run should remove auth env file, stat err = %v", err)
}
got, err := json.Marshal(firstRunResetResponse{
BootstrapToken: "bootstrap-token-placeholder",
BootstrapTokenPath: "bootstrap-token-path-placeholder",
})
if err != nil {
t.Fatalf("marshal reset-first-run response snapshot: %v", err)
}
const want = `{
"bootstrapToken":"bootstrap-token-placeholder",
"bootstrapTokenPath":"bootstrap-token-path-placeholder"
}`
assertJSONSnapshot(t, got, want)
}
func TestContract_ResetFirstRunSecurityClearsEnvBackedStatus(t *testing.T) {
t.Setenv("PULSE_DEV", "true")
t.Setenv("NODE_ENV", "")
t.Setenv("PULSE_AUTH_USER", "admin")
t.Setenv("PULSE_AUTH_PASS", "hashed-password")
record := newTokenRecord(t, "contract-reset-first-run-token-456.12345678", []string{config.ScopeSettingsWrite}, nil)
cfg := newTestConfigWithTokens(t, record)
cfg.AuthUser = "admin"
cfg.AuthPass = "hashed-password"
router := NewRouter(cfg, nil, nil, nil, nil, "1.0.0")
resetReq := httptest.NewRequest(http.MethodPost, "/api/security/dev/reset-first-run", nil)
resetReq.Header.Set("X-API-Token", "contract-reset-first-run-token-456.12345678")
resetRec := httptest.NewRecorder()
router.Handler().ServeHTTP(resetRec, resetReq)
if resetRec.Code != http.StatusOK {
t.Fatalf("reset-first-run status = %d, want 200 (%s)", resetRec.Code, resetRec.Body.String())
}
statusReq := httptest.NewRequest(http.MethodGet, "/api/security/status", nil)
statusRec := httptest.NewRecorder()
router.Handler().ServeHTTP(statusRec, statusReq)
if statusRec.Code != http.StatusOK {
t.Fatalf("security status = %d, want 200 (%s)", statusRec.Code, statusRec.Body.String())
}
var payload map[string]any
if err := json.Unmarshal(statusRec.Body.Bytes(), &payload); err != nil {
t.Fatalf("decode security status payload: %v", err)
}
if got, _ := payload["hasAuthentication"].(bool); got {
t.Fatalf("hasAuthentication = %v, want false", payload["hasAuthentication"])
}
if got, _ := payload["bootstrapTokenPath"].(string); strings.TrimSpace(got) == "" {
t.Fatalf("bootstrapTokenPath = %v, want non-empty", payload["bootstrapTokenPath"])
}
}
func TestContract_SetupScriptURLResponseJSONSnapshot(t *testing.T) {
payload := map[string]any{
"type": "pve",
"host": "https://pve.local:8006",
"url": "https://pulse.example/api/setup-script?host=https%3A%2F%2Fpve.local%3A8006&pulse_url=https%3A%2F%2Fpulse.example&type=pve",
"downloadURL": "https://pulse.example/api/setup-script?host=https%3A%2F%2Fpve.local%3A8006&pulse_url=https%3A%2F%2Fpulse.example&setup_token=setup-token-123&type=pve",
"scriptFileName": "pulse-setup-pve.sh",
"command": "curl -fsSL 'https://pulse.example/api/setup-script?host=https%3A%2F%2Fpve.local%3A8006&pulse_url=https%3A%2F%2Fpulse.example&type=pve' | { if [ \"$(id -u)\" -eq 0 ]; then PULSE_SETUP_TOKEN='setup-token-123' bash; elif command -v sudo >/dev/null 2>&1; then sudo env PULSE_SETUP_TOKEN='setup-token-123' bash; else echo \"Root privileges required. Run as root (su -) and retry.\" >&2; exit 1; fi; }",
"commandWithEnv": "curl -fsSL 'https://pulse.example/api/setup-script?host=https%3A%2F%2Fpve.local%3A8006&pulse_url=https%3A%2F%2Fpulse.example&type=pve' | { if [ \"$(id -u)\" -eq 0 ]; then PULSE_SETUP_TOKEN='setup-token-123' bash; elif command -v sudo >/dev/null 2>&1; then sudo env PULSE_SETUP_TOKEN='setup-token-123' bash; else echo \"Root privileges required. Run as root (su -) and retry.\" >&2; exit 1; fi; }",
"commandWithoutEnv": "curl -fsSL 'https://pulse.example/api/setup-script?host=https%3A%2F%2Fpve.local%3A8006&pulse_url=https%3A%2F%2Fpulse.example&type=pve' | { if [ \"$(id -u)\" -eq 0 ]; then bash; elif command -v sudo >/dev/null 2>&1; then sudo bash; else echo \"Root privileges required. Run as root (su -) and retry.\" >&2; exit 1; fi; }",
"expires": int64(1900000000),
"setupToken": "setup-token-123",
"tokenHint": "set…123",
}
got, err := json.Marshal(payload)
if err != nil {
t.Fatalf("marshal setup-script-url response: %v", err)
}
const want = `{
"command":"curl -fsSL 'https://pulse.example/api/setup-script?host=https%3A%2F%2Fpve.local%3A8006\u0026pulse_url=https%3A%2F%2Fpulse.example\u0026type=pve' | { if [ \"$(id -u)\" -eq 0 ]; then PULSE_SETUP_TOKEN='setup-token-123' bash; elif command -v sudo \u003e/dev/null 2\u003e\u00261; then sudo env PULSE_SETUP_TOKEN='setup-token-123' bash; else echo \"Root privileges required. Run as root (su -) and retry.\" \u003e\u00262; exit 1; fi; }",
"commandWithEnv":"curl -fsSL 'https://pulse.example/api/setup-script?host=https%3A%2F%2Fpve.local%3A8006\u0026pulse_url=https%3A%2F%2Fpulse.example\u0026type=pve' | { if [ \"$(id -u)\" -eq 0 ]; then PULSE_SETUP_TOKEN='setup-token-123' bash; elif command -v sudo \u003e/dev/null 2\u003e\u00261; then sudo env PULSE_SETUP_TOKEN='setup-token-123' bash; else echo \"Root privileges required. Run as root (su -) and retry.\" \u003e\u00262; exit 1; fi; }",
"commandWithoutEnv":"curl -fsSL 'https://pulse.example/api/setup-script?host=https%3A%2F%2Fpve.local%3A8006\u0026pulse_url=https%3A%2F%2Fpulse.example\u0026type=pve' | { if [ \"$(id -u)\" -eq 0 ]; then bash; elif command -v sudo \u003e/dev/null 2\u003e\u00261; then sudo bash; else echo \"Root privileges required. Run as root (su -) and retry.\" \u003e\u00262; exit 1; fi; }",
"downloadURL":"https://pulse.example/api/setup-script?host=https%3A%2F%2Fpve.local%3A8006\u0026pulse_url=https%3A%2F%2Fpulse.example\u0026setup_token=setup-token-123\u0026type=pve",
"expires":1900000000,
"host":"https://pve.local:8006",
"scriptFileName":"pulse-setup-pve.sh",
"setupToken":"setup-token-123",
"tokenHint":"set…123",
"type":"pve",
"url":"https://pulse.example/api/setup-script?host=https%3A%2F%2Fpve.local%3A8006\u0026pulse_url=https%3A%2F%2Fpulse.example\u0026type=pve"
}`
assertJSONSnapshot(t, got, want)
}
func TestContract_SecurityStatusIncludesSessionCapabilitiesDemoMode(t *testing.T) {
cfg := newTestConfigWithTokens(t)
cfg.DemoMode = true
router := NewRouter(cfg, nil, nil, nil, nil, "1.0.0")
req := httptest.NewRequest(http.MethodGet, "/api/security/status", nil)
rec := httptest.NewRecorder()
router.Handler().ServeHTTP(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("security status = %d, want 200 (%s)", rec.Code, rec.Body.String())
}
var payload map[string]any
if err := json.Unmarshal(rec.Body.Bytes(), &payload); err != nil {
t.Fatalf("decode security status payload: %v", err)
}
sessionCapabilities, ok := payload["sessionCapabilities"].(map[string]any)
if !ok {
t.Fatalf("sessionCapabilities = %#v, want object", payload["sessionCapabilities"])
}
if got, _ := sessionCapabilities["demoMode"].(bool); !got {
t.Fatalf("sessionCapabilities.demoMode = %v, want true", sessionCapabilities["demoMode"])
}
presentationPolicy, ok := payload["presentationPolicy"].(map[string]any)
if !ok {
t.Fatalf("presentationPolicy = %#v, want object", payload["presentationPolicy"])
}
for key, want := range map[string]bool{
"demoMode": true,
"readOnly": true,
"hideCommercial": true,
"hideUpgrade": true,
} {
if got, _ := presentationPolicy[key].(bool); got != want {
t.Fatalf("presentationPolicy.%s = %v, want %v", key, presentationPolicy[key], want)
}
}
}
func TestContract_SecurityStatusPresentationPolicyDefaultsHideUpgradeOutsideHosted(t *testing.T) {
cfg := newTestConfigWithTokens(t)
cfg.DemoMode = false
router := NewRouter(cfg, nil, nil, nil, nil, "1.0.0")
req := httptest.NewRequest(http.MethodGet, "/api/security/status", nil)
rec := httptest.NewRecorder()
router.Handler().ServeHTTP(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("security status = %d, want 200 (%s)", rec.Code, rec.Body.String())
}
var payload map[string]any
if err := json.Unmarshal(rec.Body.Bytes(), &payload); err != nil {
t.Fatalf("decode security status payload: %v", err)
}
presentationPolicy, ok := payload["presentationPolicy"].(map[string]any)
if !ok {
t.Fatalf("presentationPolicy = %#v, want object", payload["presentationPolicy"])
}
for key, want := range map[string]bool{
"demoMode": false,
"readOnly": false,
"hideCommercial": false,
"hideUpgrade": true,
} {
if got, _ := presentationPolicy[key].(bool); got != want {
t.Fatalf("presentationPolicy.%s = %v, want %v", key, presentationPolicy[key], want)
}
}
}
func TestContract_SecurityStatusSplitsAuditLogCapabilityFromSettingsRead(t *testing.T) {
prevAuthorizer := authpkg.GetAuthorizer()
authpkg.SetAuthorizer(&allowRulesAuthorizer{
rules: map[string]bool{
"read:audit_logs": true,
"admin:audit_logs": true,
},
})
defer authpkg.SetAuthorizer(prevAuthorizer)
cases := []struct {
name string
scopes []string
wantAuditLog bool
wantAuditWebhooksRead bool
}{
{
name: "settings read stays limited to audit webhooks",
scopes: []string{config.ScopeSettingsRead},
wantAuditLog: false,
wantAuditWebhooksRead: true,
},
{
name: "audit read unlocks audit log capability",
scopes: []string{config.ScopeSettingsRead, config.ScopeAuditRead},
wantAuditLog: true,
wantAuditWebhooksRead: true,
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
rawToken := fmt.Sprintf("contract-audit-scope-%s.12345678", strings.ReplaceAll(tc.name, " ", "-"))
record := newTokenRecord(t, rawToken, tc.scopes, nil)
cfg := newTestConfigWithTokens(t, record)
router := NewRouter(cfg, nil, nil, nil, nil, "1.0.0")
req := httptest.NewRequest(http.MethodGet, "/api/security/status", nil)
req.Header.Set("X-API-Token", rawToken)
rec := httptest.NewRecorder()
router.Handler().ServeHTTP(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("security status = %d, want 200 (%s)", rec.Code, rec.Body.String())
}
var payload struct {
SettingsCapabilities securityStatusSettingsCapabilities `json:"settingsCapabilities"`
TokenScopes []string `json:"tokenScopes"`
}
if err := json.Unmarshal(rec.Body.Bytes(), &payload); err != nil {
t.Fatalf("decode security status payload: %v", err)
}
if payload.SettingsCapabilities.AuditLog != tc.wantAuditLog {
t.Fatalf("settingsCapabilities.auditLog = %v, want %v", payload.SettingsCapabilities.AuditLog, tc.wantAuditLog)
}
if payload.SettingsCapabilities.AuditWebhooksRead != tc.wantAuditWebhooksRead {
t.Fatalf(
"settingsCapabilities.auditWebhooksRead = %v, want %v",
payload.SettingsCapabilities.AuditWebhooksRead,
tc.wantAuditWebhooksRead,
)
}
if !reflect.DeepEqual(payload.TokenScopes, tc.scopes) {
t.Fatalf("tokenScopes = %#v, want %#v", payload.TokenScopes, tc.scopes)
}
})
}
}
func TestContract_SetupScriptURLRejectsNonCanonicalRequestJSON(t *testing.T) {
handler := newTestConfigHandlers(t, &config.Config{DataPath: t.TempDir()})
req := httptest.NewRequest(
http.MethodPost,
"/api/setup-script-url",
bytes.NewBufferString(`{"type":"pve","host":"pve.local","setupToken":"unexpected"}`),
)
rec := httptest.NewRecorder()
handler.HandleSetupScriptURL(rec, req)
if rec.Code != http.StatusBadRequest {
t.Fatalf("status = %d, want 400", rec.Code)
}
if got := rec.Body.String(); got != "Invalid request\n" {
t.Fatalf("body = %q, want canonical invalid-request guidance", got)
}
}
func TestContract_SetupBootstrapRejectsPBSBackupPerms(t *testing.T) {
handler := newTestConfigHandlers(t, &config.Config{DataPath: t.TempDir()})
setupScriptReq := httptest.NewRequest(
http.MethodGet,
"/api/setup-script?type=pbs&host=https://pbs.local:8007&pulse_url=https://pulse.example&backup_perms=true",
nil,
)
setupScriptRec := httptest.NewRecorder()
handler.HandleSetupScript(setupScriptRec, setupScriptReq)
if setupScriptRec.Code != http.StatusBadRequest {
t.Fatalf("setup-script status = %d, want 400", setupScriptRec.Code)
}
if got := setupScriptRec.Body.String(); got != "backup_perms is only supported for type 'pve'\n" {
t.Fatalf("setup-script body = %q, want canonical backup-perms guidance", got)
}
setupURLReq := httptest.NewRequest(
http.MethodPost,
"/api/setup-script-url",
bytes.NewBufferString(`{"type":"pbs","host":"pbs.local","backupPerms":true}`),
)
setupURLRec := httptest.NewRecorder()
handler.HandleSetupScriptURL(setupURLRec, setupURLReq)
if setupURLRec.Code != http.StatusBadRequest {
t.Fatalf("setup-script-url status = %d, want 400", setupURLRec.Code)
}
if got := setupURLRec.Body.String(); got != "backupPerms is only supported for type 'pve'\n" {
t.Fatalf("setup-script-url body = %q, want canonical backup-perms guidance", got)
}
}
func TestContract_CanonicalAutoRegisterSourceContract(t *testing.T) {
if !isCanonicalAutoRegisterSource("agent") {
t.Fatalf("agent source should be accepted")
}
if !isCanonicalAutoRegisterSource("script") {
t.Fatalf("script source should be accepted")
}
if isCanonicalAutoRegisterSource("manual") {
t.Fatalf("manual source should be rejected")
}
}
func TestContract_CanonicalAutoRegisterTypeContract(t *testing.T) {
if !isCanonicalAutoRegisterType("pve") {
t.Fatalf("pve type should be accepted")
}
if !isCanonicalAutoRegisterType("pbs") {
t.Fatalf("pbs type should be accepted")
}
if isCanonicalAutoRegisterType("pmg") {
t.Fatalf("pmg type should be rejected")
}
}
func TestContract_CanonicalAutoRegisterTokenIDContract(t *testing.T) {
if !isCanonicalAutoRegisterTokenID("pve", "pulse-monitor@pve!pulse-homelab") {
t.Fatalf("canonical pve token id should be accepted")
}
if !isCanonicalAutoRegisterTokenID("pbs", "pulse-monitor@pbs!pulse-backup") {
t.Fatalf("canonical pbs token id should be accepted")
}
if isCanonicalAutoRegisterTokenID("pve", "pulse-monitor@pve!token") {
t.Fatalf("non-pulse-managed pve token suffix should be rejected")
}
if isCanonicalAutoRegisterTokenID("pve", "pulse@pve!token") {
t.Fatalf("non-canonical token id should be rejected")
}
if isCanonicalAutoRegisterTokenID("pve", "pulse-monitor@pbs!pulse-backup") {
t.Fatalf("cross-type token id should be rejected")
}
if isCanonicalAutoRegisterTokenID("pve", "pulse-monitor@pve!") {
t.Fatalf("empty canonical token suffix should be rejected")
}
if isCanonicalAutoRegisterTokenID("pve", "pulse-monitor@pve!pulse-") {
t.Fatalf("empty pulse-managed token slug should be rejected")
}
}
func TestContract_CanonicalAutoRegisterMatchMessageContract(t *testing.T) {
if got := canonicalAutoRegisterMatchMessage("resolved host identity"); got != "Canonical auto-register matched existing node by resolved host identity" {
t.Fatalf("resolved-host message = %q", got)
}
if got := canonicalAutoRegisterMatchMessage("DHCP continuity token identity"); got != "Canonical auto-register matched existing node by DHCP continuity token identity" {
t.Fatalf("dhcp message = %q", got)
}
if got := canonicalAutoRegisterMatchMessage("host; updated token in-place"); got != "Canonical auto-register matched existing node by host; updated token in-place" {
t.Fatalf("host-update message = %q", got)
}
if strings.Contains(canonicalAutoRegisterMatchMessage("resolved host identity"), "Secure auto-register") {
t.Fatalf("canonical match message must not preserve secure auto-register wording")
}
}
func TestContract_CanonicalAutoRegisterCompletionPayloadMessageContract(t *testing.T) {
if got := canonicalAutoRegisterCompletionPayloadMessage(); got != "Incomplete canonical auto-register token completion payload" {
t.Fatalf("completion-payload message = %q", got)
}
if strings.Contains(canonicalAutoRegisterCompletionPayloadMessage(), "secure token completion") {
t.Fatalf("canonical completion-payload message must not preserve secure wording")
}
}
func TestContract_CanonicalAutoRegisterMissingFieldsMessageContract(t *testing.T) {
if got := canonicalAutoRegisterMissingFieldsMessage("", "", false, ""); got != "Missing required canonical auto-register fields: type, host, tokenId/tokenValue, serverName" {
t.Fatalf("all-missing message = %q", got)
}
if got := canonicalAutoRegisterMissingFieldsMessage("pve", "https://pve.local:8006", true, ""); got != "Missing required canonical auto-register fields: serverName" {
t.Fatalf("serverName-only message = %q", got)
}
}
func TestContract_CanonicalAutoUnregisterMissingFieldsMessageContract(t *testing.T) {
if got := canonicalAutoUnregisterMissingFieldsMessage("", "", ""); got != "Missing required canonical auto-unregister fields: type, host, serverName" {
t.Fatalf("all-missing auto-unregister message = %q", got)
}
if got := canonicalAutoUnregisterMissingFieldsMessage("pve", "https://pve.local:8006", ""); got != "Missing required canonical auto-unregister fields: serverName" {
t.Fatalf("serverName-only auto-unregister message = %q", got)
}
}
func TestContract_CanonicalAutoRegisterDirectValidationContract(t *testing.T) {
tempDir := t.TempDir()
t.Setenv("PULSE_DATA_DIR", tempDir)
cfg := &config.Config{
DataPath: tempDir,
ConfigPath: tempDir,
}
handler := newTestConfigHandlers(t, cfg)
reqBody := AutoRegisterRequest{
Type: "pve",
Host: "https://pve.local:8006",
Source: "script",
TokenID: "pulse-monitor@pve!pulse-homelab",
TokenValue: "secret-token",
}
missingServerJSON, err := json.Marshal(reqBody)
if err != nil {
t.Fatalf("marshal missing-serverName canonical request: %v", err)
}
missingServerReq := httptest.NewRequest(http.MethodPost, "/api/auto-register", bytes.NewReader(missingServerJSON))
missingServerRec := httptest.NewRecorder()
handler.handleCanonicalAutoRegister(missingServerRec, missingServerReq, &reqBody, "127.0.0.1")
if missingServerRec.Code != http.StatusBadRequest {
t.Fatalf("missing-serverName status = %d, want 400", missingServerRec.Code)
}
if got := missingServerRec.Body.String(); got != "Missing required canonical auto-register fields: serverName\n" {
t.Fatalf("missing-serverName body = %q, want canonical missing-field guidance", got)
}
reqBody.ServerName = "pve-node-1"
reqBody.TokenValue = ""
mismatchedReq := httptest.NewRequest(http.MethodPost, "/api/auto-register", nil)
mismatchedRec := httptest.NewRecorder()
handler.handleCanonicalAutoRegister(mismatchedRec, mismatchedReq, &reqBody, "127.0.0.1")
if mismatchedRec.Code != http.StatusBadRequest {
t.Fatalf("mismatched-completion status = %d, want 400", mismatchedRec.Code)
}
if got := mismatchedRec.Body.String(); got != "tokenId and tokenValue must be provided together\n" {
t.Fatalf("mismatched-completion body = %q, want canonical token-pair guidance", got)
}
}
func TestContract_AutoRegisterResponseJSONSnapshot(t *testing.T) {
payload := map[string]any{
"status": "success",
"message": "Node pve-node-1 registered successfully at https://pve.local:8006",
"action": "use_token",
"type": "pve",
"source": "script",
"host": "https://pve.local:8006",
"nodeId": "pve-node-1",
"nodeName": "pve-node-1",
"tokenId": "pulse-monitor@pve!pulse-homelab",
"tokenValue": "secret-token",
}
got, err := json.Marshal(payload)
if err != nil {
t.Fatalf("marshal auto-register response: %v", err)
}
const want = `{
"action":"use_token",
"host":"https://pve.local:8006",
"message":"Node pve-node-1 registered successfully at https://pve.local:8006",
"nodeId":"pve-node-1",
"nodeName":"pve-node-1",
"source":"script",
"status":"success",
"tokenId":"pulse-monitor@pve!pulse-homelab",
"tokenValue":"secret-token",
"type":"pve"
}`
assertJSONSnapshot(t, got, want)
}
func TestContract_AutoUnregisterResponseJSONSnapshot(t *testing.T) {
payload := map[string]any{
"status": "success",
"message": "Node pve-node-1 removed successfully from https://pve.local:8006",
"action": "remove_connection",
"type": "pve",
"source": "script",
"host": "https://pve.local:8006",
"nodeId": "pve-node-1",
"nodeName": "pve-node-1",
"removed": true,
}
got, err := json.Marshal(payload)
if err != nil {
t.Fatalf("marshal auto-unregister response: %v", err)
}
const want = `{
"action":"remove_connection",
"host":"https://pve.local:8006",
"message":"Node pve-node-1 removed successfully from https://pve.local:8006",
"nodeId":"pve-node-1",
"nodeName":"pve-node-1",
"removed":true,
"source":"script",
"status":"success",
"type":"pve"
}`
assertJSONSnapshot(t, got, want)
}
func TestContract_AutoUnregisterNoopResponseJSONSnapshot(t *testing.T) {
payload := map[string]any{
"status": "success",
"message": "No matching node is currently configured for https://pve.local:8006",
"action": "noop",
"type": "pve",
"source": "script",
"host": "https://pve.local:8006",
"nodeId": "pve-node-1",
"nodeName": "pve-node-1",
"removed": false,
}
got, err := json.Marshal(payload)
if err != nil {
t.Fatalf("marshal auto-unregister noop response: %v", err)
}
const want = `{
"action":"noop",
"host":"https://pve.local:8006",
"message":"No matching node is currently configured for https://pve.local:8006",
"nodeId":"pve-node-1",
"nodeName":"pve-node-1",
"removed":false,
"source":"script",
"status":"success",
"type":"pve"
}`
assertJSONSnapshot(t, got, want)
}
func TestContract_AutoRegisterWebSocketEventJSONSnapshot(t *testing.T) {
payload := map[string]any{
"type": "pve",
"source": "script",
"host": "https://pve.local:8006",
"name": "pve-node-1",
"nodeId": "pve-node-1",
"nodeName": "pve-node-1",
"tokenId": "pulse-monitor@pve!pulse-homelab",
"hasToken": true,
"verifySSL": true,
"status": "connected",
}
got, err := json.Marshal(payload)
if err != nil {
t.Fatalf("marshal auto-register websocket event: %v", err)
}
const want = `{
"hasToken":true,
"host":"https://pve.local:8006",
"name":"pve-node-1",
"nodeId":"pve-node-1",
"nodeName":"pve-node-1",
"source":"script",
"status":"connected",
"tokenId":"pulse-monitor@pve!pulse-homelab",
"type":"pve",
"verifySSL":true
}`
assertJSONSnapshot(t, got, want)
}
func TestContract_CanonicalAutoRegisterEventJSONSnapshot(t *testing.T) {
payload := map[string]any{
"type": "pbs",
"source": "agent",
"host": "https://pbs.local:8007",
"name": "backup-node (2)",
"nodeId": "backup-node (2)",
"nodeName": "backup-node (2)",
"tokenId": "pulse-monitor@pbs!pulse-backup",
"hasToken": true,
"verifySSL": true,
"status": "connected",
}
got, err := json.Marshal(payload)
if err != nil {
t.Fatalf("marshal canonical /api/auto-register websocket event: %v", err)
}
const want = `{
"hasToken":true,
"host":"https://pbs.local:8007",
"name":"backup-node (2)",
"nodeId":"backup-node (2)",
"nodeName":"backup-node (2)",
"source":"agent",
"status":"connected",
"tokenId":"pulse-monitor@pbs!pulse-backup",
"type":"pbs",
"verifySSL":true
}`
assertJSONSnapshot(t, got, want)
}
func TestContract_CanonicalAutoRegisterReusedTokenResponseJSONSnapshot(t *testing.T) {
payload := map[string]any{
"status": "success",
"message": "Node pve-node-1 registered successfully at https://pve.local:8006",
"action": "use_token",
"type": "pve",
"source": "script",
"host": "https://pve.local:8006",
"nodeId": "pve-node-1",
"nodeName": "pve-node-1",
"tokenId": "pulse-monitor@pve!pulse-existing-node",
"tokenValue": "existing-token",
}
got, err := json.Marshal(payload)
if err != nil {
t.Fatalf("marshal canonical /api/auto-register response: %v", err)
}
const want = `{
"action":"use_token",
"host":"https://pve.local:8006",
"message":"Node pve-node-1 registered successfully at https://pve.local:8006",
"nodeId":"pve-node-1",
"nodeName":"pve-node-1",
"source":"script",
"status":"success",
"tokenId":"pulse-monitor@pve!pulse-existing-node",
"tokenValue":"existing-token",
"type":"pve"
}`
assertJSONSnapshot(t, got, want)
}
func TestContract_CanonicalAutoRegisterCallerProvidedTokenResponseJSONSnapshot(t *testing.T) {
payload := map[string]any{
"status": "success",
"message": "Node pve-node-1 registered successfully at https://pve.local:8006",
"action": "use_token",
"type": "pve",
"source": "agent",
"host": "https://pve.local:8006",
"nodeId": "pve-node-1",
"nodeName": "pve-node-1",
"tokenId": "pulse-monitor@pve!pulse-server",
"tokenValue": "created-locally",
}
got, err := json.Marshal(payload)
if err != nil {
t.Fatalf("marshal canonical /api/auto-register caller-provided token response: %v", err)
}
const want = `{
"action":"use_token",
"host":"https://pve.local:8006",
"message":"Node pve-node-1 registered successfully at https://pve.local:8006",
"nodeId":"pve-node-1",
"nodeName":"pve-node-1",
"source":"agent",
"status":"success",
"tokenId":"pulse-monitor@pve!pulse-server",
"tokenValue":"created-locally",
"type":"pve"
}`
assertJSONSnapshot(t, got, want)
}
func TestContract_CanonicalAutoRegisterRotatedTokenResponseJSONSnapshot(t *testing.T) {
payload := map[string]any{
"status": "success",
"message": "Node pve-node-1 registered successfully at https://pve.local:8006",
"action": "use_token",
"type": "pve",
"source": "agent",
"host": "https://pve.local:8006",
"nodeId": "pve-node-1",
"nodeName": "pve-node-1",
"tokenId": "pulse-monitor@pve!pulse-existing-node",
"tokenValue": "rotated-token",
}
got, err := json.Marshal(payload)
if err != nil {
t.Fatalf("marshal canonical /api/auto-register rotated token response: %v", err)
}
const want = `{
"action":"use_token",
"host":"https://pve.local:8006",
"message":"Node pve-node-1 registered successfully at https://pve.local:8006",
"nodeId":"pve-node-1",
"nodeName":"pve-node-1",
"source":"agent",
"status":"success",
"tokenId":"pulse-monitor@pve!pulse-existing-node",
"tokenValue":"rotated-token",
"type":"pve"
}`
assertJSONSnapshot(t, got, want)
}
func TestContract_CanonicalAutoRegisterResponseUsesCanonicalStoredNodeIdentity(t *testing.T) {
payload := map[string]any{
"status": "success",
"message": "Node pve-node-1 (2) registered successfully at https://pve.local:8006",
"action": "use_token",
"type": "pve",
"source": "agent",
"host": "https://pve.local:8006",
"nodeId": "pve-node-1 (2)",
"nodeName": "pve-node-1 (2)",
"tokenId": "pulse-monitor@pve!pulse-existing-node",
"tokenValue": "existing-token",
}
got, err := json.Marshal(payload)
if err != nil {
t.Fatalf("marshal canonical /api/auto-register disambiguated response: %v", err)
}
const want = `{
"action":"use_token",
"host":"https://pve.local:8006",
"message":"Node pve-node-1 (2) registered successfully at https://pve.local:8006",
"nodeId":"pve-node-1 (2)",
"nodeName":"pve-node-1 (2)",
"source":"agent",
"status":"success",
"tokenId":"pulse-monitor@pve!pulse-existing-node",
"tokenValue":"existing-token",
"type":"pve"
}`
assertJSONSnapshot(t, got, want)
}
func TestContract_MetricsHistoryLiveFallbackJSONSnapshot(t *testing.T) {
state := models.NewState()
state.UpdateVMsForInstance("pve1", []models.VM{{
ID: "pve1:node1:101",
VMID: 101,
Name: "vm-101",
Node: "node1",
Instance: "pve1",
Status: "running",
Type: "qemu",
CPU: 0.42,
Memory: models.Memory{
Usage: 55.0,
},
Disk: models.Disk{
Usage: 33.0,
},
}})
monitor := &monitoring.Monitor{}
setUnexportedField(t, monitor, "state", state)
setUnexportedField(t, monitor, "metricsHistory", monitoring.NewMetricsHistory(10, time.Hour))
tempDir := t.TempDir()
mtp := config.NewMultiTenantPersistence(tempDir)
if _, err := mtp.GetPersistence("default"); err != nil {
t.Fatalf("failed to init persistence: %v", err)
}
router := &Router{
monitor: monitor,
licenseHandlers: NewLicenseHandlers(mtp, false),
}
req := httptest.NewRequest(
http.MethodGet,
"/api/metrics-store/history?resourceType=vm&resourceId=pve1:node1:101&metric=cpu&start=2026-03-11T00:00:00Z&end=2026-03-12T00:00:00Z",
nil,
)
rec := httptest.NewRecorder()
router.handleMetricsHistory(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("status = %d, body=%s", rec.Code, rec.Body.String())
}
var payload map[string]any
if err := json.Unmarshal(rec.Body.Bytes(), &payload); err != nil {
t.Fatalf("unmarshal metrics history response: %v", err)
}
points, ok := payload["points"].([]any)
if !ok || len(points) != 1 {
t.Fatalf("unexpected points payload: %#v", payload["points"])
}
point, ok := points[0].(map[string]any)
if !ok {
t.Fatalf("unexpected point payload: %#v", points[0])
}
point["timestamp"] = float64(1700000000000)
payload["range"] = "24h"
payload["start"] = float64(1741651200000)
payload["end"] = float64(1741737600000)
got, err := json.Marshal(payload)
if err != nil {
t.Fatalf("marshal normalized metrics history response: %v", err)
}
const want = `{
"end":1741737600000,
"metric":"cpu",
"points":[
{
"max":42,
"min":42,
"timestamp":1700000000000,
"value":42
}
],
"range":"24h",
"resourceId":"pve1:node1:101",
"resourceType":"vm",
"source":"live",
"start":1741651200000
}`
assertJSONSnapshot(t, got, want)
}
func TestContract_MetricsHistoryAgentTemperatureLiveFallback(t *testing.T) {
state := models.NewState()
state.UpsertHost(models.Host{
ID: "agent-pve-node-1",
Hostname: "pve-node-1",
Status: "online",
Sensors: models.HostSensorSummary{
TemperatureCelsius: map[string]float64{
"cpu_package": 62.5,
},
},
})
monitor := &monitoring.Monitor{}
setUnexportedField(t, monitor, "state", state)
setUnexportedField(t, monitor, "metricsHistory", monitoring.NewMetricsHistory(10, time.Hour))
router := &Router{monitor: monitor}
req := httptest.NewRequest(
http.MethodGet,
"/api/metrics-store/history?resourceType=agent&resourceId=agent-pve-node-1&metric=temperature&range=5m",
nil,
)
rec := httptest.NewRecorder()
router.handleMetricsHistory(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("status = %d, body=%s", rec.Code, rec.Body.String())
}
var resp metricsHistoryResponse
if err := json.Unmarshal(rec.Body.Bytes(), &resp); err != nil {
t.Fatalf("failed to unmarshal response: %v", err)
}
if resp.ResourceType != "agent" {
t.Fatalf("expected resourceType agent, got %q", resp.ResourceType)
}
if resp.ResourceId != "agent-pve-node-1" {
t.Fatalf("expected resource id agent-pve-node-1, got %q", resp.ResourceId)
}
if resp.Metric != "temperature" {
t.Fatalf("expected metric temperature, got %q", resp.Metric)
}
if resp.Source != "live" {
t.Fatalf("expected source live, got %q", resp.Source)
}
if len(resp.Points) != 1 || resp.Points[0].Value != 62.5 {
t.Fatalf("expected one live temperature point 62.5, got %+v", resp.Points)
}
}
func TestContract_MetricsHistoryCanonicalizesLegacyKubernetesPodIDs(t *testing.T) {
mh := monitoring.NewMetricsHistory(1000, time.Hour)
now := time.Now().UTC().Truncate(time.Second)
legacyID := "cluster-1:pod:pod-1"
canonicalID := "k8s:" + legacyID
for i, value := range []float64{21, 34, 29} {
mh.AddGuestMetric(canonicalID, "cpu", value, now.Add(time.Duration(i-2)*10*time.Minute))
}
router := &Router{
monitor: &monitoring.Monitor{},
}
setUnexportedField(t, router.monitor, "metricsHistory", mh)
req := httptest.NewRequest(
http.MethodGet,
"/api/metrics-store/history?resourceType=k8s&resourceId="+legacyID+"&metric=cpu&range=30m",
nil,
)
rec := httptest.NewRecorder()
router.handleMetricsHistory(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("status = %d, body=%s", rec.Code, rec.Body.String())
}
var resp metricsHistoryResponse
if err := json.Unmarshal(rec.Body.Bytes(), &resp); err != nil {
t.Fatalf("failed to unmarshal response: %v", err)
}
if resp.ResourceType != "k8s" {
t.Fatalf("expected resourceType k8s, got %q", resp.ResourceType)
}
if resp.ResourceId != canonicalID {
t.Fatalf("expected canonical pod resource id %q, got %q", canonicalID, resp.ResourceId)
}
if resp.Metric != "cpu" {
t.Fatalf("expected metric cpu, got %q", resp.Metric)
}
if resp.Range != "30m" {
t.Fatalf("expected range 30m, got %q", resp.Range)
}
if resp.Source != "memory" {
t.Fatalf("expected source memory, got %q", resp.Source)
}
if len(resp.Points) != 3 {
t.Fatalf("expected 3 cpu points, got %d", len(resp.Points))
}
}
func TestContract_MetricsHistoryPhysicalDiskIOLiveWindowUsesCanonicalDiskTarget(t *testing.T) {
mh := monitoring.NewMetricsHistory(1000, time.Hour)
now := time.Now().UTC().Truncate(time.Second)
resourceID := "SERIAL884006359727"
for i, value := range []float64{1.5, 2.25, 3.0} {
mh.AddDiskMetric(resourceID, "diskread", value*1024*1024, now.Add(time.Duration(i-2)*10*time.Minute))
}
router := &Router{
monitor: &monitoring.Monitor{},
}
setUnexportedField(t, router.monitor, "metricsHistory", mh)
req := httptest.NewRequest(
http.MethodGet,
"/api/metrics-store/history?resourceType=disk&resourceId="+resourceID+"&metric=diskread&range=30m",
nil,
)
rec := httptest.NewRecorder()
router.handleMetricsHistory(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("status = %d, body=%s", rec.Code, rec.Body.String())
}
var resp metricsHistoryResponse
if err := json.Unmarshal(rec.Body.Bytes(), &resp); err != nil {
t.Fatalf("failed to unmarshal response: %v", err)
}
if resp.ResourceType != "disk" {
t.Fatalf("expected resourceType disk, got %q", resp.ResourceType)
}
if resp.ResourceId != resourceID {
t.Fatalf("expected canonical disk resource id %q, got %q", resourceID, resp.ResourceId)
}
if resp.Metric != "diskread" {
t.Fatalf("expected metric diskread, got %q", resp.Metric)
}
if resp.Range != "30m" {
t.Fatalf("expected range 30m, got %q", resp.Range)
}
if resp.Source != "memory" {
t.Fatalf("expected source memory, got %q", resp.Source)
}
if len(resp.Points) != 3 {
t.Fatalf("expected 3 diskread points, got %d", len(resp.Points))
}
}
func TestContract_PatrolStatusResponseJSONSnapshot(t *testing.T) {
lastPatrolAt := time.Date(2026, 3, 12, 9, 30, 0, 0, time.UTC)
lastActivityAt := lastPatrolAt.Add(5 * time.Minute)
nextPatrolAt := lastPatrolAt.Add(6 * time.Hour)
blockedAt := lastPatrolAt.Add(15 * time.Minute)
payload := PatrolStatusResponse{
RuntimeState: ai.PatrolRuntimeStateBlocked,
Running: false,
Enabled: true,
LastPatrolAt: &lastPatrolAt,
LastActivityAt: &lastActivityAt,
TriggerStatus: &ai.TriggerStatus{
Running: true,
PendingTriggers: 3,
CurrentInterval: 300000,
RecentEvents: 6,
IsBusyMode: true,
AlertTriggersEnabled: true,
AnomalyTriggersEnabled: true,
EventTriggersBlocked: true,
EventTriggersBlockedReason: ai.EventTriggerBlockReasonBackgroundAutomationDisabled,
EventTriggersBlockedMessage: "Automatic Patrol checks from alerts and anomalies are paused by the local development safety guard. Manual Patrol still works.",
},
NextPatrolAt: &nextPatrolAt,
LastDurationMs: 12345,
ResourcesChecked: 18,
FindingsCount: 3,
ErrorCount: 1,
Healthy: false,
IntervalMs: 21600000,
FixedCount: 2,
BlockedReason: "Awaiting AI provider configuration",
BlockedCause: string(ai.PatrolFailureCauseProviderNotConfigured),
BlockedAt: &blockedAt,
LicenseRequired: true,
LicenseStatus: "none",
UpgradeURL: "https://pulserelay.pro/upgrade?feature=ai_autofix",
Readiness: &PatrolReadinessResponse{
Status: patrolReadinessNotReady,
Ready: false,
Cause: string(ai.PatrolFailureCauseModelUnsupportedTools),
Summary: "The selected Patrol model is a reasoning-only model family that commonly does not emit tool calls.",
Provider: "ollama",
Model: "ollama:deepseek-r1:7b-llama-distill-q4_K_M",
Checks: []PatrolReadinessCheck{
{
ID: "tools",
Status: patrolReadinessNotReady,
Cause: string(ai.PatrolFailureCauseModelUnsupportedTools),
Label: "Patrol tools",
Message: "The selected Patrol model is a reasoning-only model family that commonly does not emit tool calls.",
Action: "open_provider_settings",
},
},
},
}
payload.Summary.Critical = 1
payload.Summary.Warning = 2
payload.Summary.Watch = 0
payload.Summary.Info = 4
got, err := json.Marshal(payload)
if err != nil {
t.Fatalf("marshal patrol status response: %v", err)
}
const want = `{
"runtime_state":"blocked",
"running":false,
"enabled":true,
"last_patrol_at":"2026-03-12T09:30:00Z",
"last_activity_at":"2026-03-12T09:35:00Z",
"trigger_status":{"running":true,"pending_triggers":3,"current_interval_ms":300000,"recent_events":6,"is_busy_mode":true,"alert_triggers_enabled":true,"anomaly_triggers_enabled":true,"event_triggers_blocked":true,"event_triggers_blocked_reason":"background_automation_disabled","event_triggers_blocked_message":"Automatic Patrol checks from alerts and anomalies are paused by the local development safety guard. Manual Patrol still works."},
"next_patrol_at":"2026-03-12T15:30:00Z",
"last_duration_ms":12345,
"resources_checked":18,
"findings_count":3,
"error_count":1,
"healthy":false,
"interval_ms":21600000,
"fixed_count":2,
"blocked_reason":"Awaiting AI provider configuration",
"blocked_cause":"provider_not_configured",
"blocked_at":"2026-03-12T09:45:00Z",
"license_required":true,
"license_status":"none",
"upgrade_url":"https://pulserelay.pro/upgrade?feature=ai_autofix",
"summary":{"critical":1,"warning":2,"watch":0,"info":4},
"readiness":{"status":"not_ready","ready":false,"cause":"model_unsupported_tools","summary":"The selected Patrol model is a reasoning-only model family that commonly does not emit tool calls.","provider":"ollama","model":"ollama:deepseek-r1:7b-llama-distill-q4_K_M","checks":[{"id":"tools","status":"not_ready","cause":"model_unsupported_tools","label":"Patrol tools","message":"The selected Patrol model is a reasoning-only model family that commonly does not emit tool calls.","action":"open_provider_settings"}]}
}`
assertJSONSnapshot(t, got, want)
}
func TestContract_PatrolRunRecordJSONSnapshot(t *testing.T) {
startedAt := time.Date(2026, 3, 12, 10, 0, 0, 0, time.UTC)
completedAt := startedAt.Add(90 * time.Second)
payload := ai.PatrolRunRecord{
ID: "run-1",
StartedAt: startedAt,
CompletedAt: completedAt,
DurationMs: 90000,
Type: "scoped",
TriggerReason: "alert_fired",
ScopeResourceIDs: []string{"seed-resource"},
EffectiveScopeResourceIDs: []string{},
ScopeResourceTypes: []string{"vm"},
ResourcesChecked: 4,
NodesChecked: 0,
GuestsChecked: 2,
DockerChecked: 0,
StorageChecked: 0,
HostsChecked: 0,
TrueNASChecked: 1,
PBSChecked: 0,
PMGChecked: 1,
KubernetesChecked: 1,
NewFindings: 0,
ExistingFindings: 2,
RejectedFindings: 1,
ResolvedFindings: 1,
AutoFixCount: 0,
FindingsSummary: "All clear",
FindingIDs: []string{},
ErrorCount: 0,
Status: "healthy",
TriageFlags: 3,
TriageSkippedLLM: true,
ToolCallCount: 0,
}
got, err := json.Marshal(payload)
if err != nil {
t.Fatalf("marshal patrol run record: %v", err)
}
const want = `{
"id":"run-1",
"started_at":"2026-03-12T10:00:00Z",
"completed_at":"2026-03-12T10:01:30Z",
"duration_ms":90000,
"type":"scoped",
"trigger_reason":"alert_fired",
"scope_resource_ids":["seed-resource"],
"effective_scope_resource_ids":[],
"scope_resource_types":["vm"],
"resources_checked":4,
"nodes_checked":0,
"guests_checked":2,
"docker_checked":0,
"storage_checked":0,
"hosts_checked":0,
"truenas_checked":1,
"pbs_checked":0,
"pmg_checked":1,
"kubernetes_checked":1,
"new_findings":0,
"existing_findings":2,
"rejected_findings":1,
"resolved_findings":1,
"findings_summary":"All clear",
"finding_ids":[],
"error_count":0,
"status":"healthy",
"triage_flags":3,
"triage_skipped_llm":true,
"tool_call_count":0
}`
assertJSONSnapshot(t, got, want)
}
func TestContract_ChatStreamEventJSONSnapshots(t *testing.T) {
cases := []struct {
name string
event chat.StreamEvent
want string
}{
{
name: "session",
event: mustStreamEvent(t, "session", chat.SessionData{
ID: "session-1",
}),
want: `{"type":"session","data":{"id":"session-1"}}`,
},
{
name: "content",
event: mustStreamEvent(t, "content", chat.ContentData{
Text: "hello",
}),
want: `{"type":"content","data":{"text":"hello"}}`,
},
{
name: "workflow_state_provider_start",
event: mustStreamEvent(t, "workflow_state", chat.WorkflowStateData{
Phase: "provider_start",
Message: "Waiting for assistant.",
State: "investigating",
Provider: "openrouter",
Model: "openrouter:qwen/qwen3.7-plus",
}),
want: `{"type":"workflow_state","data":{"phase":"provider_start","message":"Waiting for assistant.","state":"investigating","provider":"openrouter","model":"openrouter:qwen/qwen3.7-plus"}}`,
},
{
name: "workflow_state_provider_retry",
event: mustStreamEvent(t, "workflow_state", chat.WorkflowStateData{
Phase: "provider_retry",
Message: "Selected route connection failed before any output; retrying.",
State: "investigating",
Attempt: 2,
MaxAttempts: 2,
RetryAfterMS: 200,
}),
want: `{"type":"workflow_state","data":{"phase":"provider_retry","message":"Selected route connection failed before any output; retrying.","state":"investigating","attempt":2,"max_attempts":2,"retry_after_ms":200}}`,
},
{
name: "workflow_state_stream_idle",
event: mustStreamEvent(t, "workflow_state", chat.WorkflowStateData{
Phase: "stream_idle",
Message: chatStreamIdleProgressMessage,
}),
want: `{"type":"workflow_state","data":{"phase":"stream_idle","message":"Assistant is still working; waiting for the next stream event."}}`,
},
{
name: "tool_start",
event: mustStreamEvent(t, "tool_start", chat.ToolStartData{
ID: "tool-1",
Name: "pulse_read",
Input: `{"path":"/tmp/x.log"}`,
RawInput: `{"path":"/tmp/x.log"}`,
}),
want: `{"type":"tool_start","data":{"id":"tool-1","name":"pulse_read","input":"{\"path\":\"/tmp/x.log\"}","raw_input":"{\"path\":\"/tmp/x.log\"}"}}`,
},
{
name: "tool_progress",
event: mustStreamEvent(t, "tool_progress", chat.ToolProgressData{
ID: "tool-1",
Name: "pulse_read",
Input: `{"path":"/tmp/x.log"}`,
RawInput: `{"path":"/tmp/x.log"}`,
Phase: "running",
Message: "Reading target.",
}),
want: `{"type":"tool_progress","data":{"id":"tool-1","name":"pulse_read","input":"{\"path\":\"/tmp/x.log\"}","raw_input":"{\"path\":\"/tmp/x.log\"}","phase":"running","message":"Reading target."}}`,
},
{
name: "tool_cancel",
event: mustStreamEvent(t, "tool_cancel", chat.ToolCancelData{
ID: "tool-1",
Name: "pulse_read",
Reason: "current_resource unavailable",
}),
want: `{"type":"tool_cancel","data":{"id":"tool-1","name":"pulse_read","reason":"current_resource unavailable"}}`,
},
{
name: "tool_end",
event: mustStreamEvent(t, "tool_end", chat.ToolEndData{
ID: "tool-1",
Name: "pulse_read",
Input: `{"path":"/tmp/x.log"}`,
RawInput: `{"path":"/tmp/x.log"}`,
Output: "ok",
Success: true,
}),
want: `{"type":"tool_end","data":{"id":"tool-1","name":"pulse_read","input":"{\"path\":\"/tmp/x.log\"}","raw_input":"{\"path\":\"/tmp/x.log\"}","output":"ok","success":true}}`,
},
{
name: "approval_needed",
event: mustStreamEvent(t, "approval_needed", chat.ApprovalNeededData{
ApprovalID: "approval-1",
ToolID: "tool-2",
ToolName: "pulse_exec",
Command: "systemctl restart nginx",
RunOnHost: true,
TargetHost: "node-1",
TargetType: "agent",
TargetID: "agent-1",
Risk: "high",
Description: "Restart web service",
AuditID: "action-1",
Plan: &chat.ApprovalPlanData{
ActionID: "action-1",
RequestID: "approval-1",
Summary: "Restart web service",
RequiresApproval: true,
ApprovalPolicy: "admin",
BlastRadius: "service interruption on target",
RollbackAvailable: true,
PlanHash: "hash-1",
ExpiresAt: "2026-04-23T12:30:00Z",
},
ContextConfidence: &chat.ApprovalContextConfidenceData{
Level: "verified",
Summary: "Target was resolved to a concrete resource before approval.",
Evidence: []string{"Target identifier bound to agent-1."},
},
Preflight: &chat.ApprovalPreflightData{
Target: "agent:node-1 (agent-1)",
CurrentState: "Resolved approval target: agent:node-1 (agent-1).",
IntendedChange: "Restart web service",
DryRunAvailable: false,
DryRunSummary: "No provider-supported dry run is available for this action.",
SafetyChecks: []string{"Approval is scoped to this organization."},
VerificationSteps: []string{"Read back the target state after execution."},
GeneratedAt: "2026-04-23T12:29:00Z",
},
}),
want: `{"type":"approval_needed","data":{"approval_id":"approval-1","tool_id":"tool-2","tool_name":"pulse_exec","command":"systemctl restart nginx","run_on_host":true,"target_host":"node-1","target_type":"agent","target_id":"agent-1","risk":"high","description":"Restart web service","audit_id":"action-1","plan":{"action_id":"action-1","request_id":"approval-1","summary":"Restart web service","requires_approval":true,"approval_policy":"admin","blast_radius":"service interruption on target","rollback_available":true,"plan_hash":"hash-1","expires_at":"2026-04-23T12:30:00Z"},"context_confidence":{"level":"verified","summary":"Target was resolved to a concrete resource before approval.","evidence":["Target identifier bound to agent-1."]},"preflight":{"target":"agent:node-1 (agent-1)","current_state":"Resolved approval target: agent:node-1 (agent-1).","intended_change":"Restart web service","dry_run_available":false,"dry_run_summary":"No provider-supported dry run is available for this action.","safety_checks":["Approval is scoped to this organization."],"verification_steps":["Read back the target state after execution."],"generated_at":"2026-04-23T12:29:00Z"}}}`,
},
{
name: "question",
event: mustStreamEvent(t, "question", chat.QuestionData{
SessionID: "session-1",
QuestionID: "question-1",
Questions: []chat.Question{
{
ID: "target",
Type: "select",
Question: "Which node should I inspect?",
Header: "Target",
Options: []chat.QuestionOption{
{Label: "Node A", Value: "node-a", Description: "Primary compute node"},
{Label: "Node B", Value: "node-b", Description: "Replica node"},
},
},
},
}),
want: `{"type":"question","data":{"session_id":"session-1","question_id":"question-1","questions":[{"id":"target","type":"select","question":"Which node should I inspect?","header":"Target","options":[{"label":"Node A","value":"node-a","description":"Primary compute node"},{"label":"Node B","value":"node-b","description":"Replica node"}]}]}}`,
},
{
name: "done",
event: mustStreamEvent(t, "done", chat.DoneData{
SessionID: "session-1",
InputTokens: 120,
OutputTokens: 80,
}),
want: `{"type":"done","data":{"session_id":"session-1","input_tokens":120,"output_tokens":80}}`,
},
{
name: "error",
event: mustStreamEvent(t, "error", chat.ErrorData{
Message: "request failed",
}),
want: `{"type":"error","data":{"message":"request failed"}}`,
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
got, err := json.Marshal(tc.event)
if err != nil {
t.Fatalf("marshal stream event: %v", err)
}
assertJSONSnapshot(t, got, tc.want)
})
}
}
func TestContract_LegacyAssistantStreamIdleEventJSONSnapshot(t *testing.T) {
event := ai.StreamEvent{
Type: "workflow_state",
Data: chat.WorkflowStateData{
Phase: "stream_idle",
Message: chatStreamIdleProgressMessage,
},
}
got, err := json.Marshal(event)
if err != nil {
t.Fatalf("marshal legacy Assistant stream event: %v", err)
}
assertJSONSnapshot(t, got, `{"type":"workflow_state","data":{"phase":"stream_idle","message":"Assistant is still working; waiting for the next stream event."}}`)
}
func TestContract_AssistantChatStreamUsesClientSafeProjection(t *testing.T) {
handlerSource, err := os.ReadFile(filepath.Clean("ai_handler.go"))
if err != nil {
t.Fatalf("read ai_handler.go: %v", err)
}
serviceSource, err := os.ReadFile(filepath.Clean("../ai/chat/service.go"))
if err != nil {
t.Fatalf("read chat service: %v", err)
}
typesSource, err := os.ReadFile(filepath.Clean("../ai/chat/types.go"))
if err != nil {
t.Fatalf("read chat types: %v", err)
}
handlerText := string(handlerSource)
for _, required := range []string{
"func (h *AIHandler) HandleChat(w http.ResponseWriter, r *http.Request)",
"event, ok = event.ClientSafe()",
"json.Marshal(event)",
} {
if !strings.Contains(handlerText, required) {
t.Fatalf("Assistant chat SSE handler must encode client-safe stream projection: missing %q", required)
}
}
serviceText := string(serviceSource)
for _, required := range []string{
"func (s *Service) ExecuteStream(ctx context.Context, req ExecuteRequest, callback StreamCallback) error",
"event, ok = event.ClientSafe()",
"streamCallback(event)",
} {
if !strings.Contains(serviceText, required) {
t.Fatalf("chat service stream boundary must return client-safe stream events: missing %q", required)
}
}
typesText := string(typesSource)
for _, required := range []string{
"func (e StreamEvent) ClientSafe() (StreamEvent, bool)",
`case "thinking":`,
"return StreamEvent{}, false",
"data.Text = cleanToolCallArtifacts(data.Text)",
} {
if !strings.Contains(typesText, required) {
t.Fatalf("chat stream client-safe projection must drop thinking and raw tool-call prose: missing %q", required)
}
}
}
func TestContract_AssistantMockModeRuntimeUsesEffectiveConfig(t *testing.T) {
handlerSource, err := os.ReadFile(filepath.Clean("ai_handler.go"))
if err != nil {
t.Fatalf("read ai_handler.go: %v", err)
}
mockStreamSource, err := os.ReadFile(filepath.Clean("../ai/chat/mock_stream.go"))
if err != nil {
t.Fatalf("read chat mock stream: %v", err)
}
handlerText := string(handlerSource)
for _, required := range []string{
"func aiChatRuntimeConfigForMockMode(cfg *config.AIConfig) *config.AIConfig",
"mockmode.IsEnabled()",
"next.Enabled = true",
"latestCfg := aiChatRuntimeConfigForMockMode(h.loadAIConfig(ctx))",
"aiCfg := aiChatRuntimeConfigForMockMode(h.loadAIConfig(tenantCtx))",
"aiCfg = aiChatRuntimeConfigForMockMode(aiCfg)",
"newCfg := aiChatRuntimeConfigForMockMode(h.loadAIConfig(ctx))",
} {
if !strings.Contains(handlerText, required) {
t.Fatalf("Assistant mock-mode runtime config must stay effective-only and cover startup/sync paths: missing %q", required)
}
}
mockStreamText := string(mockStreamSource)
for _, required := range []string{
"var mockAssistantStreamPace =",
"func pauseMockAssistantStream(ctx context.Context) bool",
"time.NewTimer(mockAssistantStreamPace)",
"pauseMockAssistantStream(ctx)",
"Reading synthetic Pulse inventory.",
"Summarizing mock inventory result.",
"Reading mock device inventory.",
"Summarizing mock device count.",
"mockAssistantReadToolName",
"Composing mock Assistant response.",
} {
if !strings.Contains(mockStreamText, required) {
t.Fatalf("Assistant mock stream must pace browser-visible status/tool/content proof: missing %q", required)
}
}
}
func TestContract_PushNotificationJSONSnapshots(t *testing.T) {
cases := []struct {
name string
payload relay.PushNotificationPayload
want string
}{
{
name: "patrol_finding",
payload: relay.NewPatrolFindingNotification("finding-1", "warning", "capacity", "Disk pressure detected"),
want: `{"type":"patrol_finding","priority":"normal","title":"Disk pressure detected","body":"New warning capacity finding detected","action_type":"view_finding","action_id":"finding-1","category":"capacity","severity":"warning"}`,
},
{
name: "patrol_critical",
payload: relay.NewPatrolFindingNotification("finding-2", "critical", "performance", "CPU saturation detected"),
want: `{"type":"patrol_critical","priority":"high","title":"CPU saturation detected","body":"New critical performance finding detected","action_type":"view_finding","action_id":"finding-2","category":"performance","severity":"critical"}`,
},
{
name: "approval_request",
payload: relay.NewApprovalRequestNotification("approval-1", "Fix queued", "high"),
want: `{"type":"approval_request","priority":"high","title":"Fix queued","body":"A high-risk fix requires your approval","action_type":"approve_fix","action_id":"approval-1"}`,
},
{
name: "fix_completed_success",
payload: relay.NewFixCompletedNotification("finding-3", "CPU saturation detected", true),
want: `{"type":"fix_completed","priority":"normal","title":"CPU saturation detected","body":"Fix applied successfully","action_type":"view_fix_result","action_id":"finding-3"}`,
},
{
name: "fix_completed_failed",
payload: relay.NewFixCompletedNotification("finding-4", "Disk pressure detected", false),
want: `{"type":"fix_completed","priority":"normal","title":"Disk pressure detected","body":"Fix attempt failed — review needed","action_type":"view_fix_result","action_id":"finding-4"}`,
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
got, err := json.Marshal(tc.payload)
if err != nil {
t.Fatalf("marshal push payload: %v", err)
}
assertJSONSnapshot(t, got, tc.want)
})
}
}
func TestContract_AlertJSONSnapshot(t *testing.T) {
start := time.Date(2026, 2, 8, 13, 14, 15, 0, time.UTC)
lastSeen := start.Add(3 * time.Minute)
payload := alerts.Alert{
ID: "cluster/qemu/100-cpu",
Type: "cpu",
Level: alerts.AlertLevelWarning,
ResourceID: "cluster/qemu/100",
ResourceName: "test-vm",
Node: "pve-1",
Instance: "cpu0",
Message: "VM cpu at 95%",
Value: 95.0,
Threshold: 90.0,
StartTime: start,
LastSeen: lastSeen,
Acknowledged: false,
Metadata: map[string]interface{}{
"resourceType": "VM",
"clearThreshold": 70.0,
"unit": "%",
"monitorOnly": true,
},
}
got, err := json.Marshal(payload)
if err != nil {
t.Fatalf("marshal alert: %v", err)
}
const want = `{
"id":"cluster/qemu/100-cpu",
"type":"cpu",
"level":"warning",
"resourceId":"cluster/qemu/100",
"resourceName":"test-vm",
"node":"pve-1",
"instance":"cpu0",
"message":"VM cpu at 95%",
"value":95,
"threshold":90,
"startTime":"2026-02-08T13:14:15Z",
"lastSeen":"2026-02-08T13:17:15Z",
"acknowledged":false,
"metadata":{"clearThreshold":70,"monitorOnly":true,"resourceType":"VM","unit":"%"}
}`
assertJSONSnapshot(t, got, want)
}
func TestContract_AlertAllFieldsJSONSnapshot(t *testing.T) {
start := time.Date(2026, 2, 8, 13, 14, 15, 0, time.UTC)
lastSeen := start.Add(3 * time.Minute)
ackTime := start.Add(5 * time.Minute)
lastNotified := start.Add(2 * time.Minute)
escalationTimes := []time.Time{start.Add(1 * time.Minute), start.Add(3 * time.Minute)}
payload := alerts.Alert{
ID: "cluster/qemu/100-cpu",
Type: "cpu",
Level: alerts.AlertLevelWarning,
ResourceID: "cluster/qemu/100",
ResourceName: "test-vm",
Node: "pve-1",
NodeDisplayName: "Proxmox Node 1",
Instance: "cpu0",
Message: "VM cpu at 95%",
Value: 95.0,
Threshold: 90.0,
StartTime: start,
LastSeen: lastSeen,
Acknowledged: true,
AckTime: &ackTime,
AckUser: "admin",
Metadata: map[string]interface{}{
"resourceType": "VM",
"clearThreshold": 70.0,
"unit": "%",
"monitorOnly": true,
},
LastNotified: &lastNotified,
LastEscalation: 2,
EscalationTimes: escalationTimes,
}
got, err := json.Marshal(payload)
if err != nil {
t.Fatalf("marshal alert with all fields: %v", err)
}
const want = `{
"id":"cluster/qemu/100-cpu",
"type":"cpu",
"level":"warning",
"resourceId":"cluster/qemu/100",
"resourceName":"test-vm",
"node":"pve-1",
"nodeDisplayName":"Proxmox Node 1",
"instance":"cpu0",
"message":"VM cpu at 95%",
"value":95,
"threshold":90,
"startTime":"2026-02-08T13:14:15Z",
"lastSeen":"2026-02-08T13:17:15Z",
"acknowledged":true,
"ackTime":"2026-02-08T13:19:15Z",
"ackUser":"admin",
"metadata":{"clearThreshold":70,"monitorOnly":true,"resourceType":"VM","unit":"%"},
"lastNotified":"2026-02-08T13:16:15Z",
"lastEscalation":2,
"escalationTimes":["2026-02-08T13:15:15Z","2026-02-08T13:17:15Z"]
}`
assertJSONSnapshot(t, got, want)
}
func TestContract_ModelAlertJSONSnapshot(t *testing.T) {
start := time.Date(2026, 2, 8, 13, 14, 15, 0, time.UTC)
ackTime := start.Add(5 * time.Minute)
resolvedTime := start.Add(10 * time.Minute)
t.Run("alert", func(t *testing.T) {
payload := models.Alert{
ID: "cluster/qemu/100-cpu",
Type: "cpu",
Level: "warning",
ResourceID: "cluster/qemu/100",
ResourceName: "test-vm",
Node: "pve-1",
NodeDisplayName: "Proxmox Node 1",
Instance: "cpu0",
Message: "VM cpu at 95%",
Value: 95.0,
Threshold: 90.0,
StartTime: start,
Acknowledged: true,
AckTime: &ackTime,
AckUser: "admin",
}
got, err := json.Marshal(payload)
if err != nil {
t.Fatalf("marshal model alert: %v", err)
}
forbidden := []string{`"lastSeen"`, `"metadata"`, `"lastNotified"`, `"lastEscalation"`, `"escalationTimes"`}
for _, field := range forbidden {
if strings.Contains(string(got), field) {
t.Fatalf("model alert json unexpectedly contains %s: %s", field, string(got))
}
}
const want = `{
"id":"cluster/qemu/100-cpu",
"type":"cpu",
"level":"warning",
"resourceId":"cluster/qemu/100",
"resourceName":"test-vm",
"node":"pve-1",
"nodeDisplayName":"Proxmox Node 1",
"instance":"cpu0",
"message":"VM cpu at 95%",
"value":95,
"threshold":90,
"startTime":"2026-02-08T13:14:15Z",
"acknowledged":true,
"ackTime":"2026-02-08T13:19:15Z",
"ackUser":"admin"
}`
assertJSONSnapshot(t, got, want)
})
t.Run("resolved_alert", func(t *testing.T) {
payload := models.ResolvedAlert{
Alert: models.Alert{
ID: "cluster/qemu/100-cpu",
Type: "cpu",
Level: "warning",
ResourceID: "cluster/qemu/100",
ResourceName: "test-vm",
Node: "pve-1",
NodeDisplayName: "Proxmox Node 1",
Instance: "cpu0",
Message: "VM cpu at 95%",
Value: 95.0,
Threshold: 90.0,
StartTime: start,
Acknowledged: true,
AckTime: &ackTime,
AckUser: "admin",
},
ResolvedTime: resolvedTime,
}
got, err := json.Marshal(payload)
if err != nil {
t.Fatalf("marshal model resolved alert: %v", err)
}
forbidden := []string{`"lastSeen"`, `"metadata"`, `"lastNotified"`, `"lastEscalation"`, `"escalationTimes"`}
for _, field := range forbidden {
if strings.Contains(string(got), field) {
t.Fatalf("model resolved alert json unexpectedly contains %s: %s", field, string(got))
}
}
const want = `{
"id":"cluster/qemu/100-cpu",
"type":"cpu",
"level":"warning",
"resourceId":"cluster/qemu/100",
"resourceName":"test-vm",
"node":"pve-1",
"nodeDisplayName":"Proxmox Node 1",
"instance":"cpu0",
"message":"VM cpu at 95%",
"value":95,
"threshold":90,
"startTime":"2026-02-08T13:14:15Z",
"acknowledged":true,
"ackTime":"2026-02-08T13:19:15Z",
"ackUser":"admin",
"resolvedTime":"2026-02-08T13:24:15Z"
}`
assertJSONSnapshot(t, got, want)
})
}
func TestContract_IncidentJSONSnapshot(t *testing.T) {
start := time.Date(2026, 2, 8, 13, 14, 15, 0, time.UTC)
ackTime := start.Add(5 * time.Minute)
closedAt := start.Add(10 * time.Minute)
t.Run("open", func(t *testing.T) {
payload := memory.Incident{
ID: "incident-1",
AlertIdentifier: "cluster/qemu/100-cpu",
AlertType: "cpu",
Level: "warning",
ResourceID: "cluster/qemu/100",
ResourceName: "test-vm",
ResourceType: "guest",
Node: "pve-1",
Instance: "cpu0",
Message: "VM cpu at 95%",
Status: memory.IncidentStatusOpen,
OpenedAt: start,
Acknowledged: true,
AckUser: "admin",
AckTime: &ackTime,
Events: []memory.IncidentEvent{
{
ID: "evt-1",
Type: memory.IncidentEventAlertFired,
Timestamp: start.Add(1 * time.Minute),
Summary: "CPU alert fired",
Details: map[string]interface{}{
"type": "cpu",
"level": "warning",
"value": 95,
"threshold": 90,
},
},
{
ID: "evt-2",
Type: memory.IncidentEventAlertAcknowledged,
Timestamp: start.Add(5 * time.Minute),
Summary: "Alert acknowledged",
Details: map[string]interface{}{
"user": "admin",
},
},
},
}
got, err := json.Marshal(payload)
if err != nil {
t.Fatalf("marshal open incident: %v", err)
}
const want = `{
"id":"incident-1",
"alertIdentifier":"cluster/qemu/100-cpu",
"alertType":"cpu",
"level":"warning",
"resourceId":"cluster/qemu/100",
"resourceName":"test-vm",
"resourceType":"guest",
"node":"pve-1",
"instance":"cpu0",
"message":"VM cpu at 95%",
"status":"open",
"openedAt":"2026-02-08T13:14:15Z",
"acknowledged":true,
"ackUser":"admin",
"ackTime":"2026-02-08T13:19:15Z",
"events":[
{"id":"evt-1","type":"alert_fired","timestamp":"2026-02-08T13:15:15Z","summary":"CPU alert fired","details":{"level":"warning","threshold":90,"type":"cpu","value":95}},
{"id":"evt-2","type":"alert_acknowledged","timestamp":"2026-02-08T13:19:15Z","summary":"Alert acknowledged","details":{"user":"admin"}}
]
}`
assertJSONSnapshot(t, got, want)
})
t.Run("resolved", func(t *testing.T) {
payload := memory.Incident{
ID: "incident-1",
AlertIdentifier: "cluster/qemu/100-cpu",
AlertType: "cpu",
Level: "warning",
ResourceID: "cluster/qemu/100",
ResourceName: "test-vm",
ResourceType: "guest",
Node: "pve-1",
Instance: "cpu0",
Message: "VM cpu at 95%",
Status: memory.IncidentStatusResolved,
OpenedAt: start,
ClosedAt: &closedAt,
Acknowledged: true,
AckUser: "admin",
AckTime: &ackTime,
Events: []memory.IncidentEvent{
{
ID: "evt-1",
Type: memory.IncidentEventAlertFired,
Timestamp: start.Add(1 * time.Minute),
Summary: "CPU alert fired",
Details: map[string]interface{}{
"type": "cpu",
"level": "warning",
"value": 95,
"threshold": 90,
},
},
{
ID: "evt-2",
Type: memory.IncidentEventAlertAcknowledged,
Timestamp: start.Add(5 * time.Minute),
Summary: "Alert acknowledged",
Details: map[string]interface{}{
"user": "admin",
},
},
},
}
got, err := json.Marshal(payload)
if err != nil {
t.Fatalf("marshal resolved incident: %v", err)
}
const want = `{
"id":"incident-1",
"alertIdentifier":"cluster/qemu/100-cpu",
"alertType":"cpu",
"level":"warning",
"resourceId":"cluster/qemu/100",
"resourceName":"test-vm",
"resourceType":"guest",
"node":"pve-1",
"instance":"cpu0",
"message":"VM cpu at 95%",
"status":"resolved",
"openedAt":"2026-02-08T13:14:15Z",
"closedAt":"2026-02-08T13:24:15Z",
"acknowledged":true,
"ackUser":"admin",
"ackTime":"2026-02-08T13:19:15Z",
"events":[
{"id":"evt-1","type":"alert_fired","timestamp":"2026-02-08T13:15:15Z","summary":"CPU alert fired","details":{"level":"warning","threshold":90,"type":"cpu","value":95}},
{"id":"evt-2","type":"alert_acknowledged","timestamp":"2026-02-08T13:19:15Z","summary":"Alert acknowledged","details":{"user":"admin"}}
]
}`
assertJSONSnapshot(t, got, want)
})
}
func TestContract_IncidentEventTypeEnumSnapshot(t *testing.T) {
type envelope struct {
Type memory.IncidentEventType `json:"type"`
}
cases := []struct {
name string
typ memory.IncidentEventType
want string
}{
{name: "alert_fired", typ: memory.IncidentEventAlertFired, want: `{"type":"alert_fired"}`},
{name: "alert_acknowledged", typ: memory.IncidentEventAlertAcknowledged, want: `{"type":"alert_acknowledged"}`},
{name: "alert_unacknowledged", typ: memory.IncidentEventAlertUnacknowledged, want: `{"type":"alert_unacknowledged"}`},
{name: "alert_resolved", typ: memory.IncidentEventAlertResolved, want: `{"type":"alert_resolved"}`},
{name: "ai_analysis", typ: memory.IncidentEventAnalysis, want: `{"type":"ai_analysis"}`},
{name: "command", typ: memory.IncidentEventCommand, want: `{"type":"command"}`},
{name: "runbook", typ: memory.IncidentEventRunbook, want: `{"type":"runbook"}`},
{name: "note", typ: memory.IncidentEventNote, want: `{"type":"note"}`},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
got, err := json.Marshal(envelope{Type: tc.typ})
if err != nil {
t.Fatalf("marshal incident event type %q: %v", tc.name, err)
}
assertJSONSnapshot(t, got, tc.want)
})
}
}
func TestContract_AlertFieldNamingConsistency(t *testing.T) {
cases := []struct {
name string
typ reflect.Type
}{
{name: "alerts.Alert", typ: reflect.TypeOf(alerts.Alert{})},
{name: "memory.Incident", typ: reflect.TypeOf(memory.Incident{})},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
for i := 0; i < tc.typ.NumField(); i++ {
field := tc.typ.Field(i)
if !field.IsExported() {
continue
}
jsonTag := field.Tag.Get("json")
if jsonTag == "" || jsonTag == "-" {
continue
}
tagName := strings.Split(jsonTag, ",")[0]
if strings.Contains(tagName, "_") {
t.Fatalf("field %s on %s uses snake_case json tag %q", field.Name, tc.name, tagName)
}
}
})
}
}
func TestContract_AlertResourceTypeConsistency(t *testing.T) {
cases := []struct {
resourceType string
want []string
}{
{resourceType: "VM", want: []string{"vm", "guest"}},
{resourceType: "Container", want: []string{}},
{resourceType: "Node", want: []string{"node"}},
{resourceType: "Agent", want: []string{"agent", "node"}},
{resourceType: "Agent Disk", want: []string{}},
{resourceType: "PBS", want: []string{"pbs", "node"}},
{resourceType: "Docker Container", want: []string{}},
{resourceType: "DockerHost", want: []string{}},
{resourceType: "Docker Service", want: []string{}},
{resourceType: "Storage", want: []string{"storage"}},
{resourceType: "PMG", want: []string{"pmg", "node"}},
{resourceType: "K8s", want: []string{}},
}
for _, tc := range cases {
t.Run(tc.resourceType, func(t *testing.T) {
got := alerts.CanonicalResourceTypeKeys(tc.resourceType)
if len(tc.want) > 0 && len(got) == 0 {
t.Fatalf("resource type %q returned no canonical keys", tc.resourceType)
}
if len(tc.want) == 0 && len(got) == 0 {
return
}
if !reflect.DeepEqual(got, tc.want) {
t.Fatalf("canonical keys mismatch for %q: got %v want %v", tc.resourceType, got, tc.want)
}
})
}
}
func TestContractResourceFiltersAcceptNativeDockerAndKubernetesInventory(t *testing.T) {
raw := strings.Join([]string{
"docker-image",
"docker-volume",
"docker-network",
"docker-task",
"docker-swarm-node",
"docker-secret",
"docker-config",
"k8s-namespace",
"k8s-service",
"k8s-replicaset",
"k8s-statefulset",
"k8s-daemonset",
"k8s-job",
"k8s-cronjob",
"k8s-ingress",
"k8s-endpoint-slice",
"k8s-network-policy",
"k8s-persistent-volume",
"k8s-persistent-volume-claim",
"k8s-storage-class",
"k8s-configmap",
"k8s-secret",
"k8s-serviceaccount",
"k8s-role",
"k8s-cluster-role",
"k8s-role-binding",
"k8s-cluster-role-binding",
"k8s-resource-quota",
"k8s-limit-range",
"k8s-pod-disruption-budget",
"k8s-horizontal-pod-autoscaler",
"k8s-event",
}, ",")
if unsupported := unsupportedResourceTypeFilterTokens(raw); len(unsupported) != 0 {
t.Fatalf("native platform inventory filters should be supported, got unsupported=%v", unsupported)
}
got := parseResourceTypes(raw)
for _, resourceType := range []unifiedresources.ResourceType{
unifiedresources.ResourceTypeDockerImage,
unifiedresources.ResourceTypeDockerVolume,
unifiedresources.ResourceTypeDockerNetwork,
unifiedresources.ResourceTypeDockerTask,
unifiedresources.ResourceTypeDockerSwarmNode,
unifiedresources.ResourceTypeDockerSecret,
unifiedresources.ResourceTypeDockerConfig,
unifiedresources.ResourceTypeK8sNamespace,
unifiedresources.ResourceTypeK8sService,
unifiedresources.ResourceTypeK8sReplicaSet,
unifiedresources.ResourceTypeK8sStatefulSet,
unifiedresources.ResourceTypeK8sDaemonSet,
unifiedresources.ResourceTypeK8sJob,
unifiedresources.ResourceTypeK8sCronJob,
unifiedresources.ResourceTypeK8sIngress,
unifiedresources.ResourceTypeK8sEndpointSlice,
unifiedresources.ResourceTypeK8sNetworkPolicy,
unifiedresources.ResourceTypeK8sPV,
unifiedresources.ResourceTypeK8sPVC,
unifiedresources.ResourceTypeK8sStorageClass,
unifiedresources.ResourceTypeK8sConfigMap,
unifiedresources.ResourceTypeK8sSecret,
unifiedresources.ResourceTypeK8sServiceAccount,
unifiedresources.ResourceTypeK8sRole,
unifiedresources.ResourceTypeK8sClusterRole,
unifiedresources.ResourceTypeK8sRoleBinding,
unifiedresources.ResourceTypeK8sClusterRoleBinding,
unifiedresources.ResourceTypeK8sResourceQuota,
unifiedresources.ResourceTypeK8sLimitRange,
unifiedresources.ResourceTypeK8sPDB,
unifiedresources.ResourceTypeK8sHPA,
unifiedresources.ResourceTypeK8sEvent,
} {
if _, ok := got[resourceType]; !ok {
t.Fatalf("parseResourceTypes(%q) missing %q in %#v", raw, resourceType, got)
}
}
}
func TestContract_TenantResourcesDoNotFallbackToRawSnapshotSeeding(t *testing.T) {
now := time.Date(2026, 3, 17, 9, 0, 0, 0, time.UTC)
h := NewResourceHandlers(&config.Config{DataPath: t.TempDir()})
h.SetStateProvider(resourceStateProvider{snapshot: models.StateSnapshot{
Hosts: []models.Host{{ID: "host-default", Hostname: "default", Status: "online", LastSeen: now}},
}})
h.SetTenantStateProvider(tenantResourceStateProvider{snapshots: map[string]models.StateSnapshot{
"acme": {
Hosts: []models.Host{{ID: "host-tenant-snapshot", Hostname: "tenant-snapshot", Status: "online", LastSeen: now}},
LastUpdate: time.Time{},
},
}})
req := httptest.NewRequest(http.MethodGet, "/api/resources?type=agent", nil)
req = req.WithContext(context.WithValue(req.Context(), OrgIDContextKey, "acme"))
rec := httptest.NewRecorder()
h.HandleListResources(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("status = %d, body=%s", rec.Code, rec.Body.String())
}
const want = `{"data":[],"meta":{"page":1,"limit":50,"total":0,"totalPages":0},"aggregations":{"total":0,"byType":{},"byStatus":{},"bySource":{},"policyPosture":{"totalResources":0,"sensitivityCounts":{},"routingCounts":{},"redactionCounts":{}}}}`
if got := strings.TrimSpace(rec.Body.String()); got != want {
t.Fatalf("tenant resource fallback contract = %s, want %s", got, want)
}
}
func TestContract_ResourceListPolicyMetadata(t *testing.T) {
now := time.Date(2026, 3, 17, 10, 0, 0, 0, time.UTC)
h := NewResourceHandlers(&config.Config{DataPath: t.TempDir()})
h.SetStateProvider(resourceUnifiedSeedProvider{
snapshot: models.StateSnapshot{LastUpdate: now},
resources: []unifiedresources.Resource{
{
ID: "vm-sensitive",
Type: unifiedresources.ResourceTypeVM,
Name: "payments-vm",
Status: unifiedresources.StatusOnline,
LastSeen: now,
Sources: []unifiedresources.DataSource{unifiedresources.SourceProxmox},
Tags: []string{"customer-data"},
Identity: unifiedresources.ResourceIdentity{
Hostnames: []string{"payments.internal"},
IPAddresses: []string{"10.0.0.44"},
},
},
},
})
req := httptest.NewRequest(http.MethodGet, "/api/resources?type=vm", nil)
rec := httptest.NewRecorder()
h.HandleListResources(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("status = %d, body=%s", rec.Code, rec.Body.String())
}
var resp ResourcesResponse
if err := json.NewDecoder(rec.Body).Decode(&resp); err != nil {
t.Fatalf("decode response: %v", err)
}
if len(resp.Data) != 1 {
t.Fatalf("expected 1 resource, got %d", len(resp.Data))
}
resource := resp.Data[0]
if resource.Canonical == nil {
t.Fatal("expected canonical identity metadata in resource contract")
}
if strings.TrimSpace(resource.Canonical.DisplayName) == "" {
t.Fatal("expected canonical display name in resource contract")
}
if resource.Policy == nil {
t.Fatal("expected policy metadata in resource contract")
}
if got := resource.Policy.Sensitivity; got != unifiedresources.ResourceSensitivityRestricted {
t.Fatalf("policy sensitivity = %q, want %q", got, unifiedresources.ResourceSensitivityRestricted)
}
if got := resource.Policy.Routing.Scope; got != unifiedresources.ResourceRoutingScopeLocalOnly {
t.Fatalf("routing scope = %q, want %q", got, unifiedresources.ResourceRoutingScopeLocalOnly)
}
wantRedactions := []unifiedresources.ResourceRedactionHint{
unifiedresources.ResourceRedactionHostname,
unifiedresources.ResourceRedactionIPAddress,
unifiedresources.ResourceRedactionPlatformID,
unifiedresources.ResourceRedactionAlias,
}
if !reflect.DeepEqual(resource.Policy.Routing.Redact, wantRedactions) {
t.Fatalf("policy redact = %#v, want %#v", resource.Policy.Routing.Redact, wantRedactions)
}
if got := resource.AISafeSummary; !strings.Contains(got, "virtual machine resource;") || !strings.Contains(got, "local-only context") {
t.Fatalf("aiSafeSummary = %q", got)
}
if resp.Aggregations.PolicyPosture == nil {
t.Fatal("expected policy posture aggregation in resource list contract")
}
if got := resp.Aggregations.PolicyPosture.TotalResources; got != 1 {
t.Fatalf("policyPosture.totalResources = %d, want 1", got)
}
if got := resp.Aggregations.PolicyPosture.SensitivityCounts[unifiedresources.ResourceSensitivityRestricted]; got != 1 {
t.Fatalf("policyPosture.sensitivityCounts[restricted] = %d, want 1", got)
}
if got := resp.Aggregations.PolicyPosture.RoutingCounts[unifiedresources.ResourceRoutingScopeLocalOnly]; got != 1 {
t.Fatalf("policyPosture.routingCounts[local-only] = %d, want 1", got)
}
if got := resp.Aggregations.PolicyPosture.RedactionCounts[unifiedresources.ResourceRedactionHostname]; got != 1 {
t.Fatalf("policyPosture.redactionCounts[hostname] = %d, want 1", got)
}
}
func TestContract_ProxmoxWorkloadDiscoveryTargetUsesLinkedNodeAgent(t *testing.T) {
now := time.Date(2026, 6, 4, 20, 0, 0, 0, time.UTC)
h := NewResourceHandlers(&config.Config{DataPath: t.TempDir()})
h.SetStateProvider(resourceUnifiedSeedProvider{
snapshot: models.StateSnapshot{LastUpdate: now},
resources: []unifiedresources.Resource{
{
ID: "system-container:grafana",
Type: unifiedresources.ResourceTypeSystemContainer,
Name: "grafana",
Status: unifiedresources.StatusOnline,
LastSeen: now,
Sources: []unifiedresources.DataSource{unifiedresources.SourceProxmox},
Proxmox: &unifiedresources.ProxmoxData{
NodeName: "delly",
VMID: 124,
LinkedAgentID: "agent-delly",
},
},
{
ID: "system-container:unlinked",
Type: unifiedresources.ResourceTypeSystemContainer,
Name: "unlinked",
Status: unifiedresources.StatusOnline,
LastSeen: now,
Sources: []unifiedresources.DataSource{unifiedresources.SourceProxmox},
Proxmox: &unifiedresources.ProxmoxData{
NodeName: "delly",
VMID: 125,
},
},
},
})
req := httptest.NewRequest(http.MethodGet, "/api/resources?type=system-container", nil)
rec := httptest.NewRecorder()
h.HandleListResources(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("status = %d, body=%s", rec.Code, rec.Body.String())
}
var resp ResourcesResponse
if err := json.NewDecoder(rec.Body).Decode(&resp); err != nil {
t.Fatalf("decode response: %v", err)
}
resourcesByID := make(map[string]unifiedresources.Resource, len(resp.Data))
for _, resource := range resp.Data {
resourcesByID[resource.ID] = resource
}
linkedTarget := resourcesByID["system-container:grafana"].DiscoveryTarget
if linkedTarget == nil {
t.Fatal("expected linked Proxmox workload discovery target")
}
if linkedTarget.AgentID != "agent-delly" || linkedTarget.ResourceID != "124" {
t.Fatalf("linked discovery target = %+v, want agent-delly/124", linkedTarget)
}
if target := resourcesByID["system-container:unlinked"].DiscoveryTarget; target != nil {
t.Fatalf("expected unlinked Proxmox workload to omit discovery target, got %+v", target)
}
}
func TestContract_ResourceListUsesDeterministicNameTieBreakers(t *testing.T) {
now := time.Date(2026, 4, 11, 0, 0, 0, 0, time.UTC)
h := NewResourceHandlers(&config.Config{DataPath: t.TempDir()})
h.SetStateProvider(resourceUnifiedSeedProvider{
snapshot: models.StateSnapshot{LastUpdate: now},
resources: []unifiedresources.Resource{
{ID: "storage-c", Type: unifiedresources.ResourceTypeStorage, Name: "backup-vault-a", Status: unifiedresources.StatusOnline, LastSeen: now},
{ID: "storage-a", Type: unifiedresources.ResourceTypeStorage, Name: "backup-vault-a", Status: unifiedresources.StatusOnline, LastSeen: now},
{ID: "storage-b", Type: unifiedresources.ResourceTypeStorage, Name: "backup-vault-a", Status: unifiedresources.StatusOnline, LastSeen: now},
},
})
for attempt := 0; attempt < 2; attempt++ {
rec := httptest.NewRecorder()
req := httptest.NewRequest(http.MethodGet, "/api/resources?type=storage&page=1&limit=100", nil)
h.HandleListResources(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("attempt %d: status = %d, body=%s", attempt+1, rec.Code, rec.Body.String())
}
var resp ResourcesResponse
if err := json.NewDecoder(rec.Body).Decode(&resp); err != nil {
t.Fatalf("attempt %d: decode response: %v", attempt+1, err)
}
gotIDs := make([]string, 0, len(resp.Data))
for _, resource := range resp.Data {
gotIDs = append(gotIDs, resource.ID)
}
wantIDs := []string{"storage-a", "storage-b", "storage-c"}
if len(gotIDs) != len(wantIDs) {
t.Fatalf("attempt %d: expected %d resources, got %d (%v)", attempt+1, len(wantIDs), len(gotIDs), gotIDs)
}
for index := range wantIDs {
if gotIDs[index] != wantIDs[index] {
t.Fatalf("attempt %d: position %d = %q, want %q (got=%v)", attempt+1, index, gotIDs[index], wantIDs[index], gotIDs)
}
}
}
}
func TestContract_ResourceListAcceptsBrowserEncodedTypeCSV(t *testing.T) {
now := time.Date(2026, 4, 12, 0, 0, 0, 0, time.UTC)
h := NewResourceHandlers(&config.Config{DataPath: t.TempDir()})
h.SetStateProvider(resourceUnifiedSeedProvider{
snapshot: models.StateSnapshot{LastUpdate: now},
resources: []unifiedresources.Resource{
{ID: "agent-delly", Type: unifiedresources.ResourceTypeAgent, Name: "delly", Status: unifiedresources.StatusOnline, LastSeen: now},
{ID: "docker-host-frigate", Type: "docker-host", Name: "frigate", Status: unifiedresources.StatusOnline, LastSeen: now},
{ID: "container-frigate", Type: unifiedresources.ResourceTypeAppContainer, Name: "frigate", Status: unifiedresources.StatusOnline, LastSeen: now},
{ID: "vm-ignored", Type: unifiedresources.ResourceTypeVM, Name: "ignored-vm", Status: unifiedresources.StatusOnline, LastSeen: now},
},
})
rec := httptest.NewRecorder()
req := httptest.NewRequest(http.MethodGet, "/api/resources?type=agent%2Cdocker-host%2Capp-container&page=1&limit=100", nil)
h.HandleListResources(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("status = %d, body=%s", rec.Code, rec.Body.String())
}
var resp ResourcesResponse
if err := json.NewDecoder(rec.Body).Decode(&resp); err != nil {
t.Fatalf("decode response: %v", err)
}
gotTypes := make(map[unifiedresources.ResourceType]int)
for _, resource := range resp.Data {
gotTypes[resource.Type]++
}
if len(resp.Data) != 3 {
t.Fatalf("expected 3 encoded-filtered resources, got %d (types=%v)", len(resp.Data), gotTypes)
}
for _, typ := range []unifiedresources.ResourceType{
unifiedresources.ResourceTypeAgent,
"docker-host",
unifiedresources.ResourceTypeAppContainer,
} {
if gotTypes[typ] != 1 {
t.Fatalf("encoded type filter count for %q = %d, want 1 (types=%v)", typ, gotTypes[typ], gotTypes)
}
}
if gotTypes[unifiedresources.ResourceTypeVM] != 0 {
t.Fatalf("encoded type filter leaked VM resources: %v", gotTypes)
}
}
func TestContract_StateAndResourceListShareCanonicalMockResourceContract(t *testing.T) {
setMockModeForTest(t, true)
dataPath := t.TempDir()
hashedPassword, err := authpkg.HashPassword("password")
if err != nil {
t.Fatalf("hash password: %v", err)
}
cfg := &config.Config{
DataPath: dataPath,
MultiTenantEnabled: true,
AuthUser: "admin",
AuthPass: hashedPassword,
}
InitSessionStore(dataPath)
InitCSRFStore(dataPath)
monitor, err := monitoring.New(cfg)
if err != nil {
t.Fatalf("new monitor: %v", err)
}
t.Cleanup(func() { monitor.Stop() })
router := &Router{
config: cfg,
monitor: monitor,
persistence: config.NewConfigPersistence(dataPath),
}
handlers := NewResourceHandlers(cfg)
handlers.SetStateProvider(monitor)
stateReq := httptest.NewRequest(http.MethodGet, "/api/state", nil)
stateReq.SetBasicAuth("admin", "password")
stateRec := httptest.NewRecorder()
router.handleState(stateRec, stateReq)
if stateRec.Code != http.StatusOK {
t.Fatalf("/api/state status = %d, body=%s", stateRec.Code, stateRec.Body.String())
}
var state models.StateFrontend
if err := json.NewDecoder(stateRec.Body).Decode(&state); err != nil {
t.Fatalf("decode /api/state: %v", err)
}
listReq := httptest.NewRequest(http.MethodGet, "/api/resources?type=agent,docker-host,pbs,pmg,k8s-cluster,k8s-node&page=1&limit=100", nil)
listRec := httptest.NewRecorder()
handlers.HandleListResources(listRec, listReq)
if listRec.Code != http.StatusOK {
t.Fatalf("/api/resources status = %d, body=%s", listRec.Code, listRec.Body.String())
}
var listResp ResourcesResponse
if err := json.NewDecoder(listRec.Body).Decode(&listResp); err != nil {
t.Fatalf("decode /api/resources: %v", err)
}
allowedTypes := map[string]bool{
"agent": true,
"docker-host": true,
"k8s-cluster": true,
"k8s-node": true,
"pbs": true,
"pmg": true,
}
stateContracts := make(map[string]resourceContractSnapshot)
for _, resource := range state.Resources {
if !allowedTypes[resource.Type] {
continue
}
if resource.Type == "node" {
t.Fatalf("/api/state published legacy resource type %#v", resource)
}
if resource.Name == "pve1" || resource.Name == "edge-apps-01" {
t.Fatalf("/api/state published legacy display name %#v", resource)
}
stateContracts[resource.ID] = resourceContractSnapshot{
ID: resource.ID,
Name: resource.Name,
Type: resource.Type,
}
}
listContracts := make(map[string]resourceContractSnapshot)
for _, resource := range listResp.Data {
if !allowedTypes[string(resource.Type)] {
continue
}
listContracts[resource.ID] = resourceContractSnapshot{
ID: resource.ID,
Name: resource.Name,
Type: string(resource.Type),
}
}
if !reflect.DeepEqual(stateContracts, listContracts) {
t.Fatalf("/api/state and /api/resources resource contracts diverged: state=%#v resources=%#v", stateContracts, listContracts)
}
foundCanonicalProxmoxName := false
for _, resource := range stateContracts {
if resource.Name == "West Production A" && resource.Type == "agent" {
foundCanonicalProxmoxName = true
break
}
}
if !foundCanonicalProxmoxName {
t.Fatalf("expected canonical proxmox resource contract in /api/state, got %#v", stateContracts)
}
}
func TestContract_ResourceListCarriesTimelineAndCapabilityContracts(t *testing.T) {
now := time.Date(2026, 3, 18, 12, 0, 0, 0, time.UTC)
occurredAt := now.Add(-2 * time.Minute)
payload := ResourcesResponse{
Data: []unifiedresources.Resource{
{
ID: "vm-42",
Type: unifiedresources.ResourceTypeVM,
Name: "web-42",
Status: unifiedresources.StatusOnline,
LastSeen: now,
Capabilities: []unifiedresources.ResourceCapability{
{
Name: "restart",
Type: unifiedresources.CapabilityTypeCommon,
Description: "Restart the VM",
MinimumApprovalLevel: unifiedresources.ApprovalAdmin,
Params: []unifiedresources.CapabilityParam{
{
Name: "force",
Type: "boolean",
Required: false,
Description: "Restart without graceful shutdown",
},
},
},
},
Relationships: []unifiedresources.ResourceRelationship{
{
SourceID: "vm-42",
TargetID: "node-1",
Type: unifiedresources.RelRunsOn,
Confidence: 1,
Active: true,
Discoverer: "proxmox_adapter",
ObservedAt: now,
LastSeenAt: now,
Metadata: map[string]any{
"source": "live",
"cluster": "pve-prod",
},
},
},
RecentChanges: []unifiedresources.ResourceChange{
{
ID: "chg-42",
ObservedAt: now,
OccurredAt: &occurredAt,
ResourceID: "vm-42",
Kind: unifiedresources.ChangeStateTransition,
From: "offline",
To: "online",
SourceType: unifiedresources.SourcePlatformEvent,
SourceAdapter: unifiedresources.AdapterProxmox,
Confidence: unifiedresources.ConfidenceHigh,
RelatedResources: []string{"node-1"},
Reason: "vm started",
Metadata: map[string]any{
"source": "snapshot",
"ticket": "INC-1234",
},
},
},
FacetCounts: unifiedresources.ResourceFacetCounts{
RecentChanges: 1,
},
},
},
Meta: ResourcesMeta{
Page: 1,
Limit: 50,
Total: 1,
TotalPages: 1,
},
Aggregations: unifiedresources.ResourceStats{
Total: 1,
ByType: map[unifiedresources.ResourceType]int{unifiedresources.ResourceTypeVM: 1},
ByStatus: map[unifiedresources.ResourceStatus]int{unifiedresources.StatusOnline: 1},
BySource: map[unifiedresources.DataSource]int{unifiedresources.SourceProxmox: 1},
PolicyPosture: unifiedresources.EmptyResourcePolicyPostureSummary(),
},
}
got, err := json.Marshal(payload)
if err != nil {
t.Fatalf("marshal resource response: %v", err)
}
const want = `{
"data":[
{
"id":"vm-42",
"type":"vm",
"name":"web-42",
"status":"online",
"lastSeen":"2026-03-18T12:00:00Z",
"updatedAt":"0001-01-01T00:00:00Z",
"sources":null,
"identity":{},
"capabilities":[
{
"name":"restart",
"type":"common",
"description":"Restart the VM",
"minimumApprovalLevel":"admin",
"params":[
{
"name":"force",
"type":"boolean",
"required":false,
"isSensitive":false,
"description":"Restart without graceful shutdown"
}
]
}
],
"relationships":[
{
"sourceId":"vm-42",
"targetId":"node-1",
"type":"runs_on",
"confidence":1,
"active":true,
"discoverer":"proxmox_adapter",
"observedAt":"2026-03-18T12:00:00Z",
"lastSeenAt":"2026-03-18T12:00:00Z",
"metadata":{"cluster":"pve-prod","source":"live"}
}
],
"recentChanges":[
{
"id":"chg-42",
"observedAt":"2026-03-18T12:00:00Z",
"occurredAt":"2026-03-18T11:58:00Z",
"resourceId":"vm-42",
"kind":"state_transition",
"from":"offline",
"to":"online",
"sourceType":"platform_event",
"sourceAdapter":"proxmox_adapter",
"confidence":"high",
"relatedResources":["node-1"],
"reason":"vm started",
"metadata":{"source":"snapshot","ticket":"INC-1234"}
}
],
"facetCounts":{
"recentChanges":1
}
}
],
"meta":{"page":1,"limit":50,"total":1,"totalPages":1},
"aggregations":{"total":1,"byType":{"vm":1},"byStatus":{"online":1},"bySource":{"proxmox":1},"policyPosture":{"totalResources":0,"sensitivityCounts":{},"routingCounts":{},"redactionCounts":{}}}
}`
assertJSONSnapshot(t, got, want)
}
func TestContract_ResourceListUsesTenantStateProviderAtStartup(t *testing.T) {
cfg := &config.Config{
DataPath: t.TempDir(),
ConfigPath: t.TempDir(),
}
router := NewRouter(cfg, nil, &monitoring.MultiTenantMonitor{}, nil, nil, "1.0.0")
rec := httptest.NewRecorder()
req := httptest.NewRequest(http.MethodGet, "/api/resources?page=1&limit=100", nil)
req = req.WithContext(context.WithValue(req.Context(), OrgIDContextKey, "tenant-a"))
router.resourceHandlers.HandleListResources(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("status = %d, body=%s", rec.Code, rec.Body.String())
}
}
func TestContract_ResourceCapabilitiesJSONSnapshot(t *testing.T) {
payload := struct {
ResourceID string `json:"resourceId"`
Capabilities []unifiedresources.ResourceCapability `json:"capabilities"`
Count int `json:"count"`
}{
ResourceID: "vm:42",
Capabilities: []unifiedresources.ResourceCapability{
{
Name: "restart",
Type: unifiedresources.CapabilityTypeCommon,
Description: "Restart the VM",
MinimumApprovalLevel: unifiedresources.ApprovalAdmin,
},
},
Count: 1,
}
got, err := json.Marshal(payload)
if err != nil {
t.Fatalf("marshal resource capabilities response: %v", err)
}
const want = `{
"resourceId":"vm:42",
"capabilities":[
{
"name":"restart",
"type":"common",
"description":"Restart the VM",
"minimumApprovalLevel":"admin"
}
],
"count":1
}`
assertJSONSnapshot(t, got, want)
}
func TestContract_AgentExecWebSocketRejectsUnboundToken(t *testing.T) {
rawToken := "contract-agent-unbound-token.12345678"
record := newTokenRecord(t, rawToken, []string{config.ScopeAgentExec}, nil)
cfg := newTestConfigWithTokens(t, record)
router := NewRouter(cfg, nil, nil, nil, nil, "1.0.0")
ts := newIPv4HTTPServer(t, router.Handler())
defer ts.Close()
conn, _, err := websocket.DefaultDialer.Dial(wsURLForHTTP(ts.URL)+"/api/agent/ws", wsHeadersForHTTP(t, ts.URL))
if err != nil {
t.Fatalf("Dial: %v", err)
}
defer conn.Close()
regMsg, err := agentexec.NewMessage(agentexec.MsgTypeAgentRegister, "", agentexec.AgentRegisterPayload{
AgentID: "agent-node-1",
Hostname: "node-1",
Version: "1.0.0",
Platform: "linux",
Token: rawToken,
})
if err != nil {
t.Fatalf("NewMessage: %v", err)
}
if err := conn.WriteJSON(regMsg); err != nil {
t.Fatalf("WriteJSON: %v", err)
}
reg := readRegisteredPayload(t, conn)
if reg.Success {
t.Fatalf("expected unbound agent exec token registration to fail closed")
}
}
func TestContract_AgentExecWebSocketAcceptsLegacyHostnameBinding(t *testing.T) {
rawToken := "contract-agent-legacy-token.12345678"
record := newTokenRecord(t, rawToken, []string{config.ScopeAgentExec}, map[string]string{"bound_hostname": "node-1"})
cfg := newTestConfigWithTokens(t, record)
router := NewRouter(cfg, nil, nil, nil, nil, "1.0.0")
ts := newIPv4HTTPServer(t, router.Handler())
defer ts.Close()
conn, _, err := websocket.DefaultDialer.Dial(wsURLForHTTP(ts.URL)+"/api/agent/ws", wsHeadersForHTTP(t, ts.URL))
if err != nil {
t.Fatalf("Dial: %v", err)
}
defer conn.Close()
regMsg, err := agentexec.NewMessage(agentexec.MsgTypeAgentRegister, "", agentexec.AgentRegisterPayload{
AgentID: "agent-node-1",
Hostname: "node-1",
Version: "1.0.0",
Platform: "linux",
Token: rawToken,
})
if err != nil {
t.Fatalf("NewMessage: %v", err)
}
if err := conn.WriteJSON(regMsg); err != nil {
t.Fatalf("WriteJSON: %v", err)
}
reg := readRegisteredPayload(t, conn)
if !reg.Success {
t.Fatalf("expected legacy hostname binding to resolve to canonical agent binding, got %q", reg.Message)
}
}
func TestContract_AgentExecWebSocketBindsProxmoxInstallTokenOnFirstUse(t *testing.T) {
rawToken := "contract-agent-proxmox-install-token.12345678"
record := newTokenRecord(t, rawToken, []string{config.ScopeAgentExec, config.ScopeAgentReport}, map[string]string{
"install_type": "pve",
"issued_via": agentInstallIssuedViaConfig,
})
cfg := newTestConfigWithTokens(t, record)
router := NewRouter(cfg, nil, nil, nil, nil, "1.0.0")
ts := newIPv4HTTPServer(t, router.Handler())
defer ts.Close()
conn, _, err := websocket.DefaultDialer.Dial(wsURLForHTTP(ts.URL)+"/api/agent/ws", wsHeadersForHTTP(t, ts.URL))
if err != nil {
t.Fatalf("Dial: %v", err)
}
regMsg, err := agentexec.NewMessage(agentexec.MsgTypeAgentRegister, "", agentexec.AgentRegisterPayload{
AgentID: "agent-delly",
Hostname: "delly",
Version: "1.0.0",
Platform: "linux",
Token: rawToken,
})
if err != nil {
conn.Close()
t.Fatalf("NewMessage: %v", err)
}
if err := conn.WriteJSON(regMsg); err != nil {
conn.Close()
t.Fatalf("WriteJSON: %v", err)
}
reg := readRegisteredPayload(t, conn)
conn.Close()
if !reg.Success {
t.Fatalf("expected Pulse-minted Proxmox install token to bind on first command registration, got %q", reg.Message)
}
config.Mu.Lock()
if got := cfg.APITokens[0].Metadata["bound_hostname"]; got != "delly" {
config.Mu.Unlock()
t.Fatalf("bound_hostname = %q, want delly", got)
}
if got := cfg.APITokens[0].Metadata["bound_agent_id"]; got != "agent-delly" {
config.Mu.Unlock()
t.Fatalf("bound_agent_id = %q, want agent-delly", got)
}
config.Mu.Unlock()
conn, _, err = websocket.DefaultDialer.Dial(wsURLForHTTP(ts.URL)+"/api/agent/ws", wsHeadersForHTTP(t, ts.URL))
if err != nil {
t.Fatalf("Dial rebound: %v", err)
}
defer conn.Close()
regMsg, err = agentexec.NewMessage(agentexec.MsgTypeAgentRegister, "", agentexec.AgentRegisterPayload{
AgentID: "agent-other",
Hostname: "other-host",
Version: "1.0.0",
Platform: "linux",
Token: rawToken,
})
if err != nil {
t.Fatalf("NewMessage rebound: %v", err)
}
if err := conn.WriteJSON(regMsg); err != nil {
t.Fatalf("WriteJSON rebound: %v", err)
}
reg = readRegisteredPayload(t, conn)
if reg.Success {
t.Fatalf("expected first-use-bound Proxmox install token to reject a different command agent")
}
}
func TestContract_AdminBypassFailsClosedOutsideDevMode(t *testing.T) {
t.Setenv("ALLOW_ADMIN_BYPASS", "1")
t.Setenv("PULSE_DEV", "")
t.Setenv("NODE_ENV", "production")
resetAdminBypassState()
if adminBypassEnabled() {
t.Fatal("expected admin bypass to fail closed outside development mode")
}
}
func TestContract_ResourceRelationshipsJSONSnapshot(t *testing.T) {
now := time.Date(2026, 3, 18, 17, 0, 0, 0, time.UTC)
payload := struct {
ResourceID string `json:"resourceId"`
Relationships []unifiedresources.ResourceRelationship `json:"relationships"`
Count int `json:"count"`
}{
ResourceID: "vm:42",
Relationships: []unifiedresources.ResourceRelationship{
{
SourceID: "vm:42",
TargetID: "node-1",
Type: unifiedresources.RelRunsOn,
Confidence: 1,
Active: true,
Discoverer: "proxmox_adapter",
ObservedAt: now,
LastSeenAt: now,
Metadata: map[string]any{
"source": "live",
"cluster": "pve-prod",
},
},
},
Count: 1,
}
got, err := json.Marshal(payload)
if err != nil {
t.Fatalf("marshal resource relationships response: %v", err)
}
const want = `{
"resourceId":"vm:42",
"relationships":[
{
"sourceId":"vm:42",
"targetId":"node-1",
"type":"runs_on",
"confidence":1,
"active":true,
"discoverer":"proxmox_adapter",
"observedAt":"2026-03-18T17:00:00Z",
"lastSeenAt":"2026-03-18T17:00:00Z",
"metadata":{"cluster":"pve-prod","source":"live"}
}
],
"count":1
}`
assertJSONSnapshot(t, got, want)
}
func TestContract_ResourceTimelineJSONSnapshot(t *testing.T) {
now := time.Date(2026, 3, 18, 17, 0, 0, 0, time.UTC)
payload := struct {
ResourceID string `json:"resourceId"`
RecentChanges []unifiedresources.ResourceChange `json:"recentChanges"`
Count int `json:"count"`
}{
ResourceID: "vm:42",
RecentChanges: []unifiedresources.ResourceChange{
{
ID: "chg-42",
ResourceID: "vm:42",
ObservedAt: now,
OccurredAt: &now,
Kind: unifiedresources.ChangeStateTransition,
From: "offline",
To: "online",
SourceType: unifiedresources.SourcePlatformEvent,
SourceAdapter: unifiedresources.AdapterProxmox,
Confidence: unifiedresources.ConfidenceHigh,
RelatedResources: []string{"node-1"},
Reason: "vm started",
Metadata: map[string]any{"source": "snapshot", "ticket": "INC-1234"},
},
},
Count: 1,
}
got, err := json.Marshal(payload)
if err != nil {
t.Fatalf("marshal resource timeline response: %v", err)
}
const want = `{
"resourceId":"vm:42",
"recentChanges":[
{
"id":"chg-42",
"observedAt":"2026-03-18T17:00:00Z",
"occurredAt":"2026-03-18T17:00:00Z",
"resourceId":"vm:42",
"kind":"state_transition",
"from":"offline",
"to":"online",
"sourceType":"platform_event",
"sourceAdapter":"proxmox_adapter",
"confidence":"high",
"relatedResources":["node-1"],
"reason":"vm started",
"metadata":{"source":"snapshot","ticket":"INC-1234"}
}
],
"count":1
}`
assertJSONSnapshot(t, got, want)
}
func TestContract_ResourceTimelineRelationshipJSONSnapshot(t *testing.T) {
now := time.Date(2026, 3, 18, 17, 5, 0, 0, time.UTC)
payload := struct {
ResourceID string `json:"resourceId"`
RecentChanges []unifiedresources.ResourceChange `json:"recentChanges"`
Count int `json:"count"`
}{
ResourceID: "vm:42",
RecentChanges: []unifiedresources.ResourceChange{
{
ID: "chg-relationship-42",
ObservedAt: now,
OccurredAt: &now,
ResourceID: "vm:42",
Kind: unifiedresources.ChangeRelationship,
From: "node-1",
To: "node-2",
SourceType: unifiedresources.SourcePulseDiff,
SourceAdapter: unifiedresources.AdapterProxmox,
Confidence: unifiedresources.ConfidenceHigh,
RelatedResources: []string{"db:alpha", "service:beta"},
Reason: "relationship updated",
Metadata: map[string]any{
"edgeType": "depends_on",
"active": true,
},
},
},
Count: 1,
}
got, err := json.Marshal(payload)
if err != nil {
t.Fatalf("marshal resource timeline relationship response: %v", err)
}
const want = `{
"resourceId":"vm:42",
"recentChanges":[
{
"id":"chg-relationship-42",
"observedAt":"2026-03-18T17:05:00Z",
"occurredAt":"2026-03-18T17:05:00Z",
"resourceId":"vm:42",
"kind":"relationship_change",
"from":"node-1",
"to":"node-2",
"sourceType":"pulse_diff",
"sourceAdapter":"proxmox_adapter",
"confidence":"high",
"relatedResources":["db:alpha","service:beta"],
"reason":"relationship updated",
"metadata":{"active":true,"edgeType":"depends_on"}
}
],
"count":1
}`
assertJSONSnapshot(t, got, want)
}
func TestContract_ResourceTimelineRestartJSONSnapshot(t *testing.T) {
now := time.Date(2026, 3, 18, 17, 10, 0, 0, time.UTC)
payload := struct {
ResourceID string `json:"resourceId"`
RecentChanges []unifiedresources.ResourceChange `json:"recentChanges"`
Count int `json:"count"`
}{
ResourceID: "container:7",
RecentChanges: []unifiedresources.ResourceChange{
{
ID: "chg-restart-7",
ObservedAt: now,
OccurredAt: &now,
ResourceID: "container:7",
Kind: unifiedresources.ChangeRestart,
From: "online|docker.restartCount=1|docker.uptimeSeconds=3600",
To: "online|docker.restartCount=2|docker.uptimeSeconds=120",
SourceType: unifiedresources.SourcePlatformEvent,
SourceAdapter: unifiedresources.AdapterDocker,
Confidence: unifiedresources.ConfidenceHigh,
RelatedResources: []string{"node:1", "service:api"},
Reason: "resource restart detected",
Metadata: map[string]any{
"changedFields": []string{"docker.restartCount", "docker.uptimeSeconds"},
},
},
},
Count: 1,
}
got, err := json.Marshal(payload)
if err != nil {
t.Fatalf("marshal resource timeline restart response: %v", err)
}
const want = `{
"resourceId":"container:7",
"recentChanges":[
{
"id":"chg-restart-7",
"observedAt":"2026-03-18T17:10:00Z",
"occurredAt":"2026-03-18T17:10:00Z",
"resourceId":"container:7",
"kind":"restart",
"from":"online|docker.restartCount=1|docker.uptimeSeconds=3600",
"to":"online|docker.restartCount=2|docker.uptimeSeconds=120",
"sourceType":"platform_event",
"sourceAdapter":"docker_adapter",
"confidence":"high",
"relatedResources":["node:1","service:api"],
"reason":"resource restart detected",
"metadata":{"changedFields":["docker.restartCount","docker.uptimeSeconds"]}
}
],
"count":1
}`
assertJSONSnapshot(t, got, want)
}
func TestContract_ResourceTimelineAnomalyJSONSnapshot(t *testing.T) {
now := time.Date(2026, 3, 18, 17, 12, 0, 0, time.UTC)
payload := struct {
ResourceID string `json:"resourceId"`
RecentChanges []unifiedresources.ResourceChange `json:"recentChanges"`
Count int `json:"count"`
}{
ResourceID: "storage:1",
RecentChanges: []unifiedresources.ResourceChange{
{
ID: "chg-anomaly-1",
ObservedAt: now,
OccurredAt: &now,
ResourceID: "storage:1",
Kind: unifiedresources.ChangeAnomaly,
From: "none",
To: "capacity_runway_low[warning]:PBS datastore archive is READ_ONLY",
SourceType: unifiedresources.SourcePulseDiff,
SourceAdapter: unifiedresources.AdapterProxmox,
Confidence: unifiedresources.ConfidenceHigh,
RelatedResources: []string{"node-2", "service:db"},
Reason: "resource incident changed",
Metadata: map[string]any{
"changedFields": []string{"incidents"},
},
},
},
Count: 1,
}
got, err := json.Marshal(payload)
if err != nil {
t.Fatalf("marshal resource timeline anomaly response: %v", err)
}
const want = `{
"resourceId":"storage:1",
"recentChanges":[
{
"id":"chg-anomaly-1",
"observedAt":"2026-03-18T17:12:00Z",
"occurredAt":"2026-03-18T17:12:00Z",
"resourceId":"storage:1",
"kind":"metric_anomaly",
"from":"none",
"to":"capacity_runway_low[warning]:PBS datastore archive is READ_ONLY",
"sourceType":"pulse_diff",
"sourceAdapter":"proxmox_adapter",
"confidence":"high",
"relatedResources":["node-2","service:db"],
"reason":"resource incident changed",
"metadata":{"changedFields":["incidents"]}
}
],
"count":1
}`
assertJSONSnapshot(t, got, want)
}
func TestContract_ResourceFacetsJSONSnapshot(t *testing.T) {
now := time.Date(2026, 3, 18, 17, 0, 0, 0, time.UTC)
payload := struct {
ResourceID string `json:"resourceId"`
Capabilities []unifiedresources.ResourceCapability `json:"capabilities,omitempty"`
Relationships []unifiedresources.ResourceRelationship `json:"relationships,omitempty"`
RecentChanges []unifiedresources.ResourceChange `json:"recentChanges"`
Counts struct {
RecentChanges int `json:"recentChanges"`
RecentChangeKinds map[unifiedresources.ChangeKind]int `json:"recentChangeKinds"`
RecentChangeSourceTypes map[unifiedresources.ChangeSourceType]int `json:"recentChangeSourceTypes"`
RecentChangeSourceAdapters map[unifiedresources.ChangeSourceAdapter]int `json:"recentChangeSourceAdapters"`
} `json:"counts"`
}{
ResourceID: "vm:42",
Capabilities: []unifiedresources.ResourceCapability{
{
Name: "restart",
Type: unifiedresources.CapabilityTypeCommon,
Description: "Restart the VM",
MinimumApprovalLevel: unifiedresources.ApprovalAdmin,
},
},
Relationships: []unifiedresources.ResourceRelationship{
{
SourceID: "vm:42",
TargetID: "node-1",
Type: unifiedresources.RelRunsOn,
Confidence: 1,
Active: true,
Discoverer: "proxmox_adapter",
ObservedAt: now,
LastSeenAt: now,
Metadata: map[string]any{
"source": "live",
"cluster": "pve-prod",
},
},
},
RecentChanges: []unifiedresources.ResourceChange{
{
ID: "chg-42",
ResourceID: "vm:42",
ObservedAt: now,
OccurredAt: &now,
Kind: unifiedresources.ChangeStateTransition,
From: "offline",
To: "online",
SourceType: unifiedresources.SourcePlatformEvent,
SourceAdapter: unifiedresources.AdapterProxmox,
Confidence: unifiedresources.ConfidenceHigh,
RelatedResources: []string{"node-1"},
Metadata: map[string]any{
"source": "snapshot",
"ticket": "INC-1234",
},
},
},
Counts: struct {
RecentChanges int `json:"recentChanges"`
RecentChangeKinds map[unifiedresources.ChangeKind]int `json:"recentChangeKinds"`
RecentChangeSourceTypes map[unifiedresources.ChangeSourceType]int `json:"recentChangeSourceTypes"`
RecentChangeSourceAdapters map[unifiedresources.ChangeSourceAdapter]int `json:"recentChangeSourceAdapters"`
}{
RecentChanges: 3,
RecentChangeKinds: map[unifiedresources.ChangeKind]int{unifiedresources.ChangeRestart: 1, unifiedresources.ChangeAnomaly: 2},
RecentChangeSourceTypes: map[unifiedresources.ChangeSourceType]int{
unifiedresources.SourcePlatformEvent: 1,
unifiedresources.SourcePulseDiff: 2,
},
RecentChangeSourceAdapters: map[unifiedresources.ChangeSourceAdapter]int{
unifiedresources.AdapterProxmox: 1,
unifiedresources.AdapterDocker: 2,
},
},
}
got, err := json.Marshal(payload)
if err != nil {
t.Fatalf("marshal resource facets response: %v", err)
}
const want = `{
"resourceId":"vm:42",
"capabilities":[
{
"name":"restart",
"type":"common",
"description":"Restart the VM",
"minimumApprovalLevel":"admin"
}
],
"relationships":[
{
"sourceId":"vm:42",
"targetId":"node-1",
"type":"runs_on",
"confidence":1,
"active":true,
"discoverer":"proxmox_adapter",
"observedAt":"2026-03-18T17:00:00Z",
"lastSeenAt":"2026-03-18T17:00:00Z",
"metadata":{"cluster":"pve-prod","source":"live"}
}
],
"recentChanges":[
{
"id":"chg-42",
"observedAt":"2026-03-18T17:00:00Z",
"occurredAt":"2026-03-18T17:00:00Z",
"resourceId":"vm:42",
"kind":"state_transition",
"from":"offline",
"to":"online",
"sourceType":"platform_event",
"sourceAdapter":"proxmox_adapter",
"confidence":"high",
"relatedResources":["node-1"],
"metadata":{"source":"snapshot","ticket":"INC-1234"}
}
],
"counts":{
"recentChanges":3,
"recentChangeKinds":{
"metric_anomaly":2,
"restart":1
},
"recentChangeSourceTypes":{
"platform_event":1,
"pulse_diff":2
},
"recentChangeSourceAdapters":{
"docker_adapter":2,
"proxmox_adapter":1
}
}
}`
assertJSONSnapshot(t, got, want)
}
func TestContract_ResourceFacetsDeriveCanonicalParentRelationship(t *testing.T) {
now := time.Date(2026, 3, 18, 17, 0, 0, 0, time.UTC)
parentID := "k8s-cluster-1"
payload := struct {
ResourceID string `json:"resourceId"`
Relationships []unifiedresources.ResourceRelationship `json:"relationships,omitempty"`
}{
ResourceID: "agent-1",
Relationships: unifiedresources.ResourceRelationshipsWithCanonicalParent(unifiedresources.Resource{
ID: "agent-1",
Type: unifiedresources.ResourceTypeAgent,
ParentID: &parentID,
LastSeen: now,
}),
}
got, err := json.Marshal(payload)
if err != nil {
t.Fatalf("marshal derived parent relationship facets response: %v", err)
}
const want = `{
"resourceId":"agent-1",
"relationships":[
{
"sourceId":"agent-1",
"targetId":"k8s-cluster-1",
"type":"owned_by",
"confidence":1,
"active":true,
"discoverer":"resource_registry",
"observedAt":"2026-03-18T17:00:00Z",
"lastSeenAt":"2026-03-18T17:00:00Z",
"metadata":{"source":"parentId"}
}
]
}`
assertJSONSnapshot(t, got, want)
}
func TestContract_ActionPlanJSONSnapshot(t *testing.T) {
now := time.Date(2026, 5, 3, 10, 0, 0, 0, time.UTC)
resource := unifiedresources.Resource{
ID: "vm:42",
Type: unifiedresources.ResourceTypeVM,
Name: "web-42",
Status: unifiedresources.StatusWarning,
LastSeen: now,
UpdatedAt: now,
Sources: []unifiedresources.DataSource{unifiedresources.SourceProxmox},
Capabilities: []unifiedresources.ResourceCapability{
{
Name: "restart",
Type: unifiedresources.CapabilityTypeCommon,
Description: "Restart the VM",
MinimumApprovalLevel: unifiedresources.ApprovalAdmin,
InternalHandler: "proxmox.vm.restart",
Params: []unifiedresources.CapabilityParam{
{Name: "mode", Type: "string", Required: true, Enum: []string{"graceful", "force"}},
},
},
},
Relationships: []unifiedresources.ResourceRelationship{
{
SourceID: "vm:42",
TargetID: "node-1",
Type: unifiedresources.RelRunsOn,
Confidence: 1,
Active: true,
Discoverer: "proxmox_adapter",
ObservedAt: now,
LastSeenAt: now,
},
},
}
req := unifiedresources.ActionRequest{
RequestID: "agent-run-123",
ResourceID: "vm:42",
CapabilityName: "restart",
Params: map[string]any{"mode": "graceful"},
Reason: "Recover after confirmed outage",
RequestedBy: "agent:oncall-helper",
}
plan, err := (actionplanner.Planner{Now: func() time.Time { return now }}).Plan(req, resource)
if err != nil {
t.Fatalf("plan action: %v", err)
}
got, err := json.Marshal(plan)
if err != nil {
t.Fatalf("marshal action plan: %v", err)
}
const want = `{
"actionId":"act_8f428171be2762cec97fd4546a291d5f",
"requestId":"agent-run-123",
"allowed":true,
"requiresApproval":true,
"approvalPolicy":"admin",
"predictedBlastRadius":["vm:42","node-1"],
"rollbackAvailable":false,
"message":"Plan created for restart on web-42. Execution requires admin approval and is not performed by this endpoint.",
"plannedAt":"2026-05-03T10:00:00Z",
"expiresAt":"2026-05-03T10:05:00Z",
"resourceVersion":"resource:sha256:54fb6f0264f42e0f2724e513",
"policyVersion":"policy:sha256:0bce3cd2df181ace685598eb",
"planHash":"sha256:69631faa9da67496a8b5953de2bc2dceb7b2aab5b582be3d1536e5d67683791b",
"preflight":{
"target":"vm:42",
"currentState":"web-42 is warning",
"intendedChange":"Restart the VM",
"dryRunAvailable":false,
"dryRunSummary":"No provider-supported dry run is advertised for this capability.",
"safetyChecks":[
"Resource was resolved from the unified resource registry.",
"Capability is advertised by the resource contract.",
"This endpoint plans only; it does not approve or execute the action.",
"Execution requires admin approval."
],
"verificationSteps":[
"Refresh the resource and confirm the expected state after execution.",
"Review /api/audit/actions/act_8f428171be2762cec97fd4546a291d5f/events for lifecycle evidence."
],
"generatedAt":"2026-05-03T10:00:00Z"
}
}`
assertJSONSnapshot(t, got, want)
}
func TestContract_ActionPlanAuditLifecycleSnapshot(t *testing.T) {
now := time.Date(2026, 5, 3, 10, 0, 0, 0, time.UTC)
resource := unifiedresources.Resource{
ID: "vm:42",
Type: unifiedresources.ResourceTypeVM,
Name: "web-42",
Status: unifiedresources.StatusWarning,
LastSeen: now,
UpdatedAt: now,
Sources: []unifiedresources.DataSource{unifiedresources.SourceProxmox},
Capabilities: []unifiedresources.ResourceCapability{
{
Name: "restart",
Type: unifiedresources.CapabilityTypeCommon,
Description: "Restart the VM",
MinimumApprovalLevel: unifiedresources.ApprovalAdmin,
InternalHandler: "proxmox.vm.restart",
Params: []unifiedresources.CapabilityParam{
{Name: "mode", Type: "string", Required: true, Enum: []string{"graceful", "force"}},
},
},
},
}
req := unifiedresources.ActionRequest{
RequestID: "agent-run-123",
ResourceID: "vm:42",
CapabilityName: "restart",
Params: map[string]any{"mode": "graceful"},
Reason: "Recover after confirmed outage",
RequestedBy: "agent:oncall-helper",
}
plan, err := (actionplanner.Planner{Now: func() time.Time { return now }}).Plan(req, resource)
if err != nil {
t.Fatalf("plan action: %v", err)
}
store := unifiedresources.NewMemoryStore()
if err := persistActionPlanAudit(store, req, plan); err != nil {
t.Fatalf("persist action plan audit: %v", err)
}
audits, err := store.GetActionAudits("vm:42", time.Time{}, 10)
if err != nil {
t.Fatalf("GetActionAudits: %v", err)
}
events, err := store.GetActionLifecycleEvents(plan.ActionID, time.Time{}, 10)
if err != nil {
t.Fatalf("GetActionLifecycleEvents: %v", err)
}
payload := struct {
Audit struct {
ID string `json:"id"`
State unifiedresources.ActionState `json:"state"`
ResourceID string `json:"resourceId"`
RequestID string `json:"requestId"`
RequestedBy string `json:"requestedBy"`
ApprovalPolicy unifiedresources.ActionApprovalLevel `json:"approvalPolicy"`
PlanHash string `json:"planHash"`
PreflightSummary string `json:"preflightSummary"`
} `json:"audit"`
Events []struct {
State unifiedresources.ActionState `json:"state"`
Actor string `json:"actor"`
Message string `json:"message"`
} `json:"events"`
}{}
if len(audits) != 1 {
t.Fatalf("audits len = %d, want 1", len(audits))
}
payload.Audit.ID = audits[0].ID
payload.Audit.State = audits[0].State
payload.Audit.ResourceID = audits[0].Request.ResourceID
payload.Audit.RequestID = audits[0].Request.RequestID
payload.Audit.RequestedBy = audits[0].Request.RequestedBy
payload.Audit.ApprovalPolicy = audits[0].Plan.ApprovalPolicy
payload.Audit.PlanHash = audits[0].Plan.PlanHash
if audits[0].Plan.Preflight != nil {
payload.Audit.PreflightSummary = audits[0].Plan.Preflight.DryRunSummary
}
for _, event := range events {
payload.Events = append(payload.Events, struct {
State unifiedresources.ActionState `json:"state"`
Actor string `json:"actor"`
Message string `json:"message"`
}{
State: event.State,
Actor: event.Actor,
Message: event.Message,
})
}
got, err := json.Marshal(payload)
if err != nil {
t.Fatalf("marshal persisted action audit contract: %v", err)
}
const want = `{
"audit":{
"id":"act_7eed41cdc58507f340151d2497707eca",
"state":"pending_approval",
"resourceId":"vm:42",
"requestId":"agent-run-123",
"requestedBy":"agent:oncall-helper",
"approvalPolicy":"admin",
"planHash":"sha256:f60417e39f967eb0803af0a3f2e2abd70f20d8a77a09c2e36976bdda34b6dbaf",
"preflightSummary":"No provider-supported dry run is advertised for this capability."
},
"events":[
{
"state":"pending_approval",
"actor":"agent:oncall-helper",
"message":"Action is waiting for approval before execution."
},
{
"state":"planned",
"actor":"agent:oncall-helper",
"message":"Action plan created."
}
]
}`
assertJSONSnapshot(t, got, want)
}
func TestContract_ActionDecisionJSONSnapshot(t *testing.T) {
now := time.Date(2026, 5, 4, 15, 0, 0, 0, time.UTC)
plannedAt := now.Add(-time.Minute)
record := unifiedresources.ActionAuditRecord{
ID: "act_decision_contract",
CreatedAt: plannedAt,
UpdatedAt: plannedAt,
State: unifiedresources.ActionStatePending,
Request: unifiedresources.ActionRequest{
RequestID: "agent-run-approve",
ResourceID: "vm:42",
CapabilityName: "restart",
Reason: "Recover after confirmed outage",
RequestedBy: "agent:oncall-helper",
},
Plan: unifiedresources.ActionPlan{
ActionID: "act_decision_contract",
RequestID: "agent-run-approve",
Allowed: true,
RequiresApproval: true,
ApprovalPolicy: unifiedresources.ApprovalAdmin,
RollbackAvailable: false,
PlannedAt: plannedAt,
ExpiresAt: now.Add(4 * time.Minute),
ResourceVersion: "resource:sha256:contract",
PolicyVersion: "policy:sha256:contract",
PlanHash: "sha256:contract",
Preflight: &unifiedresources.ActionPreflight{
Target: "vm:42",
CurrentState: "web-42 is warning",
IntendedChange: "Restart the VM",
DryRunAvailable: false,
DryRunSummary: "No provider-supported dry run is advertised for this capability.",
SafetyChecks: []string{
"Resource was resolved from the unified resource registry.",
"This endpoint plans only; it does not approve or execute the action.",
},
VerificationSteps: []string{
"Review /api/audit/actions/act_decision_contract/events for lifecycle evidence.",
},
GeneratedAt: plannedAt,
},
},
}
updated, event, err := unifiedresources.ApplyActionDecision(record, unifiedresources.ActionApprovalRecord{
Actor: "operator@example.com",
Outcome: unifiedresources.OutcomeApproved,
Reason: "inside maintenance window",
}, now)
if err != nil {
t.Fatalf("apply action decision: %v", err)
}
response := actionDecisionResponse{
ActionID: updated.ID,
State: updated.State,
Approval: updated.Approvals[len(updated.Approvals)-1],
Audit: updated,
}
payload := struct {
Response actionDecisionResponse `json:"response"`
Event struct {
ActionID string `json:"actionId"`
State unifiedresources.ActionState `json:"state"`
Actor string `json:"actor"`
Message string `json:"message"`
Result *unifiedresources.ExecutionResult `json:"result,omitempty"`
Method unifiedresources.ApprovalMethod `json:"method"`
Outcome unifiedresources.ApprovalOutcome `json:"outcome"`
Policy unifiedresources.ActionApprovalLevel `json:"policy"`
} `json:"event"`
}{Response: response}
payload.Event.ActionID = event.ActionID
payload.Event.State = event.State
payload.Event.Actor = event.Actor
payload.Event.Message = event.Message
payload.Event.Result = response.Audit.Result
payload.Event.Method = response.Approval.Method
payload.Event.Outcome = response.Approval.Outcome
payload.Event.Policy = response.Audit.Plan.ApprovalPolicy
got, err := json.Marshal(payload)
if err != nil {
t.Fatalf("marshal action decision contract: %v", err)
}
const want = `{
"response":{
"actionId":"act_decision_contract",
"state":"approved",
"approval":{
"actor":"operator@example.com",
"method":"api",
"timestamp":"2026-05-04T15:00:00Z",
"outcome":"approved",
"reason":"inside maintenance window"
},
"audit":{
"id":"act_decision_contract",
"createdAt":"2026-05-04T14:59:00Z",
"updatedAt":"2026-05-04T15:00:00Z",
"state":"approved",
"request":{
"requestId":"agent-run-approve",
"resourceId":"vm:42",
"capabilityName":"restart",
"reason":"Recover after confirmed outage",
"requestedBy":"agent:oncall-helper"
},
"plan":{
"actionId":"act_decision_contract",
"requestId":"agent-run-approve",
"allowed":true,
"requiresApproval":true,
"approvalPolicy":"admin",
"rollbackAvailable":false,
"plannedAt":"2026-05-04T14:59:00Z",
"expiresAt":"2026-05-04T15:04:00Z",
"resourceVersion":"resource:sha256:contract",
"policyVersion":"policy:sha256:contract",
"planHash":"sha256:contract",
"preflight":{
"target":"vm:42",
"currentState":"web-42 is warning",
"intendedChange":"Restart the VM",
"dryRunAvailable":false,
"dryRunSummary":"No provider-supported dry run is advertised for this capability.",
"safetyChecks":[
"Resource was resolved from the unified resource registry.",
"This endpoint plans only; it does not approve or execute the action."
],
"verificationSteps":[
"Review /api/audit/actions/act_decision_contract/events for lifecycle evidence."
],
"generatedAt":"2026-05-04T14:59:00Z"
}
},
"approvals":[
{
"actor":"operator@example.com",
"method":"api",
"timestamp":"2026-05-04T15:00:00Z",
"outcome":"approved",
"reason":"inside maintenance window"
}
],
"verificationOutcome":{
"status":"unknown"
}
}
},
"event":{
"actionId":"act_decision_contract",
"state":"approved",
"actor":"operator@example.com",
"message":"Action approved. Execution remains pending a separate execution contract.",
"method":"api",
"outcome":"approved",
"policy":"admin"
}
}`
assertJSONSnapshot(t, got, want)
}
func TestContract_ActionExecutionJSONSnapshot(t *testing.T) {
startedAt := time.Date(2026, 5, 4, 15, 30, 0, 0, time.UTC)
completedAt := startedAt.Add(30 * time.Second)
plannedAt := startedAt.Add(-2 * time.Minute)
approvedAt := startedAt.Add(-time.Minute)
record := unifiedresources.ActionAuditRecord{
ID: "act_execution_contract",
CreatedAt: plannedAt,
UpdatedAt: approvedAt,
State: unifiedresources.ActionStateApproved,
Request: unifiedresources.ActionRequest{
RequestID: "agent-run-execute",
ResourceID: "vm:42",
CapabilityName: "restart",
Reason: "Recover after confirmed outage",
RequestedBy: "agent:oncall-helper",
},
Plan: unifiedresources.ActionPlan{
ActionID: "act_execution_contract",
RequestID: "agent-run-execute",
Allowed: true,
RequiresApproval: true,
ApprovalPolicy: unifiedresources.ApprovalAdmin,
RollbackAvailable: false,
PlannedAt: plannedAt,
ExpiresAt: startedAt.Add(3 * time.Minute),
ResourceVersion: "resource:sha256:contract",
PolicyVersion: "policy:sha256:contract",
PlanHash: "sha256:contract",
Preflight: &unifiedresources.ActionPreflight{
Target: "vm:42",
CurrentState: "web-42 is warning",
IntendedChange: "Restart the VM",
DryRunAvailable: false,
DryRunSummary: "No provider-supported dry run is advertised for this capability.",
SafetyChecks: []string{
"Resource was resolved from the unified resource registry.",
"Execution must use POST /api/actions/{id}/execute after approval.",
},
VerificationSteps: []string{
"Review /api/audit/actions/act_execution_contract/events for lifecycle evidence.",
},
GeneratedAt: plannedAt,
},
},
Approvals: []unifiedresources.ActionApprovalRecord{
{
Actor: "operator@example.com",
Method: unifiedresources.MethodAPI,
Timestamp: approvedAt,
Outcome: unifiedresources.OutcomeApproved,
Reason: "inside maintenance window",
},
},
}
started, startEvent, err := unifiedresources.BeginActionExecution(record, "operator@example.com", startedAt)
if err != nil {
t.Fatalf("begin action execution: %v", err)
}
completed, doneEvent, err := unifiedresources.CompleteActionExecution(started, &unifiedresources.ExecutionResult{
Success: true,
Output: "restart dispatched",
}, "operator@example.com", completedAt)
if err != nil {
t.Fatalf("complete action execution: %v", err)
}
response := actionExecutionResponse{
ActionID: completed.ID,
State: completed.State,
Result: completed.Result,
Audit: completed,
}
payload := struct {
Response actionExecutionResponse `json:"response"`
Events []struct {
ActionID string `json:"actionId"`
State unifiedresources.ActionState `json:"state"`
Actor string `json:"actor"`
Message string `json:"message"`
} `json:"events"`
}{Response: response}
for _, event := range []unifiedresources.ActionLifecycleEvent{startEvent, doneEvent} {
payload.Events = append(payload.Events, struct {
ActionID string `json:"actionId"`
State unifiedresources.ActionState `json:"state"`
Actor string `json:"actor"`
Message string `json:"message"`
}{
ActionID: event.ActionID,
State: event.State,
Actor: event.Actor,
Message: event.Message,
})
}
got, err := json.Marshal(payload)
if err != nil {
t.Fatalf("marshal action execution contract: %v", err)
}
const want = `{
"response":{
"actionId":"act_execution_contract",
"state":"completed",
"result":{
"success":true,
"output":"restart dispatched"
},
"audit":{
"id":"act_execution_contract",
"createdAt":"2026-05-04T15:28:00Z",
"updatedAt":"2026-05-04T15:30:30Z",
"state":"completed",
"request":{
"requestId":"agent-run-execute",
"resourceId":"vm:42",
"capabilityName":"restart",
"reason":"Recover after confirmed outage",
"requestedBy":"agent:oncall-helper"
},
"plan":{
"actionId":"act_execution_contract",
"requestId":"agent-run-execute",
"allowed":true,
"requiresApproval":true,
"approvalPolicy":"admin",
"rollbackAvailable":false,
"plannedAt":"2026-05-04T15:28:00Z",
"expiresAt":"2026-05-04T15:33:00Z",
"resourceVersion":"resource:sha256:contract",
"policyVersion":"policy:sha256:contract",
"planHash":"sha256:contract",
"preflight":{
"target":"vm:42",
"currentState":"web-42 is warning",
"intendedChange":"Restart the VM",
"dryRunAvailable":false,
"dryRunSummary":"No provider-supported dry run is advertised for this capability.",
"safetyChecks":[
"Resource was resolved from the unified resource registry.",
"Execution must use POST /api/actions/{id}/execute after approval."
],
"verificationSteps":[
"Review /api/audit/actions/act_execution_contract/events for lifecycle evidence."
],
"generatedAt":"2026-05-04T15:28:00Z"
}
},
"approvals":[
{
"actor":"operator@example.com",
"method":"api",
"timestamp":"2026-05-04T15:29:00Z",
"outcome":"approved",
"reason":"inside maintenance window"
}
],
"result":{
"success":true,
"output":"restart dispatched"
},
"verificationOutcome":{
"status":"unknown"
}
}
},
"events":[
{
"actionId":"act_execution_contract",
"state":"executing",
"actor":"operator@example.com",
"message":"Action execution started."
},
{
"actionId":"act_execution_contract",
"state":"completed",
"actor":"operator@example.com",
"message":"Action execution completed."
}
]
}`
assertJSONSnapshot(t, got, want)
}
func TestContract_ActionDryRunOnlyExecutionErrorJSONSnapshot(t *testing.T) {
now := time.Now().UTC().Truncate(time.Second)
h := NewResourceHandlers(&config.Config{DataPath: t.TempDir()})
store, err := h.getStore("default")
if err != nil {
t.Fatalf("get store: %v", err)
}
record := unifiedresources.ActionAuditRecord{
ID: "act_dry_run_contract",
CreatedAt: now.Add(-time.Minute),
UpdatedAt: now.Add(-time.Minute),
State: unifiedresources.ActionStatePlanned,
Request: unifiedresources.ActionRequest{
RequestID: "agent-run-dry-run",
ResourceID: "vm:42",
CapabilityName: "restart",
Reason: "Inspect possible restart remediation",
RequestedBy: "agent:oncall-helper",
},
Plan: unifiedresources.ActionPlan{
ActionID: "act_dry_run_contract",
RequestID: "agent-run-dry-run",
Allowed: true,
RequiresApproval: false,
ApprovalPolicy: unifiedresources.ApprovalDryRun,
RollbackAvailable: false,
PlannedAt: now.Add(-time.Minute),
ExpiresAt: now.Add(4 * time.Minute),
ResourceVersion: "resource:sha256:dry-run-contract",
PolicyVersion: "policy:sha256:dry-run-contract",
PlanHash: "sha256:dry-run-contract",
Preflight: &unifiedresources.ActionPreflight{
Target: "vm:42",
CurrentState: "web-42 is warning",
IntendedChange: "Dry-run only restart inspection",
DryRunAvailable: true,
DryRunSummary: "Provider advertised dry-run only; no execution is allowed.",
SafetyChecks: []string{
"Dry-run-only plans are not executable.",
},
VerificationSteps: []string{
"Review /api/audit/actions/act_dry_run_contract/events for lifecycle evidence.",
},
GeneratedAt: now.Add(-time.Minute),
},
},
}
if err := store.RecordActionAudit(record); err != nil {
t.Fatalf("record action audit: %v", err)
}
req := httptest.NewRequest(http.MethodPost, "/api/actions/act_dry_run_contract/execute", bytes.NewBufferString(`{}`))
req.SetPathValue("id", "act_dry_run_contract")
rec := httptest.NewRecorder()
h.HandleExecuteAction(rec, req)
if rec.Code != http.StatusConflict {
t.Fatalf("status = %d, want %d; body=%s", rec.Code, http.StatusConflict, rec.Body.String())
}
// The action endpoints now emit the agent-stable error
// envelope (slice 58 refactor): the stable code lives under
// the top-level `error` field and the human message lives
// under `message`. The previous APIError shape (code under
// `code`, message under `error`) is gone for these handlers
// because they joined the agent surface.
var envelope struct {
Error string `json:"error"`
Message string `json:"message"`
}
if err := json.Unmarshal(rec.Body.Bytes(), &envelope); err != nil {
t.Fatalf("decode dry-run execution error: %v", err)
}
payload := struct {
Status int `json:"status"`
ErrorKey string `json:"error"`
Message string `json:"message"`
}{
Status: rec.Code,
ErrorKey: envelope.Error,
Message: envelope.Message,
}
got, err := json.Marshal(payload)
if err != nil {
t.Fatalf("marshal dry-run execution error contract: %v", err)
}
const want = `{
"status":409,
"error":"action_dry_run_only",
"message":"Action plan is dry-run only and cannot be executed"
}`
assertJSONSnapshot(t, got, want)
gotAudit, ok, err := store.GetActionAudit("act_dry_run_contract")
if err != nil {
t.Fatalf("GetActionAudit: %v", err)
}
if !ok || gotAudit.State != unifiedresources.ActionStateFailed || gotAudit.Result == nil || gotAudit.Result.Success || !strings.HasPrefix(gotAudit.Result.ErrorMessage, "action_dry_run_only:") {
t.Fatalf("dry-run audit mutated: ok=%v state=%q result=%#v", ok, gotAudit.State, gotAudit.Result)
}
events, err := store.GetActionLifecycleEvents("act_dry_run_contract", time.Time{}, 10)
if err != nil {
t.Fatalf("GetActionLifecycleEvents: %v", err)
}
if len(events) != 1 || events[0].State != unifiedresources.ActionStateFailed || !strings.HasPrefix(events[0].Message, "action_dry_run_only:") {
t.Fatalf("dry-run execution should append one failed lifecycle event, got %#v", events)
}
}
func TestContract_APIActionExecutionRevalidatesPlanFreshness(t *testing.T) {
source, err := os.ReadFile("actions.go")
if err != nil {
t.Fatalf("read actions.go: %v", err)
}
src := string(source)
for _, snippet := range []string{
"if unified.IsPermanentActionExecutionRefusal(err)",
"if err := h.validateActionPlanFresh(orgID, record); err != nil",
"errors.Is(err, unified.ErrActionPlanDrift)",
"recordRefusedActionExecution(store, record, actor, now, err)",
"unified.RefuseActionExecution(record, reason, actor, now)",
"writeJSONError(w, http.StatusConflict, agentcapabilities.AgentErrCodeActionPlanDrift",
} {
if !strings.Contains(src, snippet) {
t.Fatalf("actions.go must pin API execute plan freshness guard snippet %q", snippet)
}
}
if strings.Index(src, "if err := h.validateActionPlanFresh(orgID, record); err != nil") >
strings.Index(src, "started, startEvent, err := unified.BeginActionExecution(record, actor, now)") {
t.Fatal("HandleExecuteAction must validate plan freshness before entering executing state or calling the executor")
}
}
func readAgentCapabilitiesManifestSource(t *testing.T) string {
t.Helper()
source, err := os.ReadFile(filepath.Join("..", "agentcapabilities", "manifest.go"))
if err != nil {
t.Fatalf("read shared agent capabilities manifest: %v", err)
}
return string(source)
}
func TestContract_ExecuteActionCapabilityDeclaresPlanExpired(t *testing.T) {
manifest := agentcapabilities.CanonicalManifest()
var executeAction agentcapabilities.Capability
for _, cap := range manifest.Capabilities {
if cap.Name == agentcapabilities.ExecuteActionCapabilityName {
executeAction = cap
break
}
}
if executeAction.Name == "" {
t.Fatalf("agent capabilities manifest must declare %s", agentcapabilities.ExecuteActionCapabilityName)
}
if !stringSliceContains(executeAction.ErrorCodes, agentcapabilities.AgentErrCodeActionPlanExpired) {
t.Error("execute_action manifest must declare action_plan_expired so agents can branch on permanent expired-plan refusals")
}
}
func TestContract_ResourceTimelineEndpointsIncludeRelatedChanges(t *testing.T) {
now := time.Date(2026, 4, 25, 22, 15, 0, 0, time.UTC)
h := NewResourceHandlers(&config.Config{DataPath: t.TempDir()})
h.SetStateProvider(resourceUnifiedSeedProvider{
snapshot: models.StateSnapshot{LastUpdate: now},
resources: []unifiedresources.Resource{
{
ID: "node-contract-relationship",
Type: unifiedresources.ResourceTypeAgent,
Name: "node-contract-relationship",
Status: unifiedresources.StatusOnline,
LastSeen: now,
},
{
ID: "vm-contract-relationship",
Type: unifiedresources.ResourceTypeVM,
Name: "vm-contract-relationship",
Status: unifiedresources.StatusOnline,
LastSeen: now,
},
},
})
store, err := h.getStore("default")
if err != nil {
t.Fatalf("get resource store: %v", err)
}
for _, change := range []unifiedresources.ResourceChange{
{
ID: "change-related-contract",
ResourceID: "vm-contract-relationship",
ObservedAt: now,
Kind: unifiedresources.ChangeRestart,
SourceType: unifiedresources.SourcePlatformEvent,
SourceAdapter: unifiedresources.AdapterProxmox,
Confidence: unifiedresources.ConfidenceHigh,
RelatedResources: []string{" node-contract-relationship "},
},
{
ID: "change-direct-contract",
ResourceID: "node-contract-relationship",
ObservedAt: now.Add(-time.Minute),
Kind: unifiedresources.ChangeStateTransition,
SourceType: unifiedresources.SourcePulseDiff,
SourceAdapter: unifiedresources.AdapterProxmox,
Confidence: unifiedresources.ConfidenceMedium,
},
} {
if err := store.RecordChange(change); err != nil {
t.Fatalf("record %s: %v", change.ID, err)
}
}
timelineRec := httptest.NewRecorder()
timelineReq := httptest.NewRequest(http.MethodGet, "/api/resources/node-contract-relationship/timeline?limit=10", nil)
h.HandleResourceRoutes(timelineRec, timelineReq)
if timelineRec.Code != http.StatusOK {
t.Fatalf("timeline status = %d, body=%s", timelineRec.Code, timelineRec.Body.String())
}
var timeline struct {
ResourceID string `json:"resourceId"`
RecentChanges []unifiedresources.ResourceChange `json:"recentChanges"`
Count int `json:"count"`
}
if err := json.NewDecoder(timelineRec.Body).Decode(&timeline); err != nil {
t.Fatalf("decode relationship-aware timeline: %v", err)
}
if timeline.ResourceID != "node-contract-relationship" || timeline.Count != 2 || len(timeline.RecentChanges) != 2 {
t.Fatalf("unexpected relationship-aware timeline: %#v", timeline)
}
if timeline.RecentChanges[0].ID != "change-related-contract" || timeline.RecentChanges[0].ResourceID != "vm-contract-relationship" {
t.Fatalf("timeline did not preserve related originating resource: %#v", timeline.RecentChanges)
}
facetsRec := httptest.NewRecorder()
facetsReq := httptest.NewRequest(http.MethodGet, "/api/resources/node-contract-relationship/facets?kind=restart&limit=10", nil)
h.HandleResourceRoutes(facetsRec, facetsReq)
if facetsRec.Code != http.StatusOK {
t.Fatalf("facets status = %d, body=%s", facetsRec.Code, facetsRec.Body.String())
}
var facets struct {
ResourceID string `json:"resourceId"`
RecentChanges []unifiedresources.ResourceChange `json:"recentChanges"`
Counts struct {
RecentChanges int `json:"recentChanges"`
RecentChangeKinds map[unifiedresources.ChangeKind]int `json:"recentChangeKinds"`
RecentAdapters map[unifiedresources.ChangeSourceAdapter]int `json:"recentChangeSourceAdapters"`
} `json:"counts"`
}
if err := json.NewDecoder(facetsRec.Body).Decode(&facets); err != nil {
t.Fatalf("decode relationship-aware facets: %v", err)
}
if facets.ResourceID != "node-contract-relationship" || facets.Counts.RecentChanges != 1 || len(facets.RecentChanges) != 1 {
t.Fatalf("unexpected relationship-aware facets: %#v", facets)
}
if facets.RecentChanges[0].ID != "change-related-contract" {
t.Fatalf("facets did not include related restart: %#v", facets.RecentChanges)
}
if got := facets.Counts.RecentChangeKinds[unifiedresources.ChangeRestart]; got != 1 {
t.Fatalf("restart facet count = %d, want 1", got)
}
if got := facets.Counts.RecentAdapters[unifiedresources.AdapterProxmox]; got != 1 {
t.Fatalf("adapter facet count = %d, want 1", got)
}
}
func TestContract_ResourceTimelineRejectsInvalidSourceAdapter(t *testing.T) {
_, err := unifiedresources.ParseResourceChangeFilters(nil, nil, []string{"unsupported_adapter"})
if err == nil {
t.Fatal("expected invalid sourceAdapter to be rejected")
}
}
func TestContract_UnifiedActionAuditsJSONSnapshot(t *testing.T) {
now := time.Date(2026, 3, 18, 16, 0, 0, 0, time.UTC)
payload := struct {
Audits []unifiedresources.ActionAuditRecord `json:"audits"`
Count int `json:"count"`
ResourceID string `json:"resourceId,omitempty"`
}{
Audits: []unifiedresources.ActionAuditRecord{
{
ID: "action-1",
CreatedAt: now,
UpdatedAt: now,
State: unifiedresources.ActionStateCompleted,
Request: unifiedresources.ActionRequest{
RequestID: "req-1",
ResourceID: "vm:42",
CapabilityName: "restart",
Reason: "maintenance",
RequestedBy: "agent:ops",
},
Plan: unifiedresources.ActionPlan{
ActionID: "action-1",
RequestID: "req-1",
Allowed: true,
RequiresApproval: false,
ApprovalPolicy: unifiedresources.ApprovalNone,
RollbackAvailable: false,
PlannedAt: now,
ExpiresAt: now.Add(5 * time.Minute),
ResourceVersion: "rv-1",
PolicyVersion: "pv-1",
PlanHash: "hash-1",
Preflight: &unifiedresources.ActionPreflight{
Target: "vm:42",
CurrentState: "online",
IntendedChange: "restart",
DryRunAvailable: false,
DryRunSummary: "No provider-supported dry run is available for this action.",
SafetyChecks: []string{"Approval and execution are scoped to vm:42."},
VerificationSteps: []string{"Read back VM status after execution."},
GeneratedAt: now,
},
},
Approvals: []unifiedresources.ActionApprovalRecord{
{
Actor: "admin@example.com",
Method: unifiedresources.MethodUI,
Timestamp: now.Add(time.Minute),
Outcome: unifiedresources.OutcomeApproved,
Reason: "approved",
},
},
Result: &unifiedresources.ExecutionResult{
Success: true,
Output: "done",
},
VerificationOutcome: unifiedresources.VerificationOutcome{
Status: unifiedresources.VerificationUnknown,
},
},
},
Count: 1,
ResourceID: "vm:42",
}
got, err := json.Marshal(payload)
if err != nil {
t.Fatalf("marshal action audits response: %v", err)
}
const want = `{
"audits":[
{
"id":"action-1",
"createdAt":"2026-03-18T16:00:00Z",
"updatedAt":"2026-03-18T16:00:00Z",
"state":"completed",
"request":{
"requestId":"req-1",
"resourceId":"vm:42",
"capabilityName":"restart",
"reason":"maintenance",
"requestedBy":"agent:ops"
},
"plan":{
"actionId":"action-1",
"requestId":"req-1",
"allowed":true,
"requiresApproval":false,
"approvalPolicy":"none",
"rollbackAvailable":false,
"plannedAt":"2026-03-18T16:00:00Z",
"expiresAt":"2026-03-18T16:05:00Z",
"resourceVersion":"rv-1",
"policyVersion":"pv-1",
"planHash":"hash-1",
"preflight":{
"target":"vm:42",
"currentState":"online",
"intendedChange":"restart",
"dryRunAvailable":false,
"dryRunSummary":"No provider-supported dry run is available for this action.",
"safetyChecks":["Approval and execution are scoped to vm:42."],
"verificationSteps":["Read back VM status after execution."],
"generatedAt":"2026-03-18T16:00:00Z"
}
},
"approvals":[
{
"actor":"admin@example.com",
"method":"ui",
"timestamp":"2026-03-18T16:01:00Z",
"outcome":"approved",
"reason":"approved"
}
],
"result":{
"success":true,
"output":"done"
},
"verificationOutcome":{
"status":"unknown"
}
}
],
"count":1,
"resourceId":"vm:42"
}`
assertJSONSnapshot(t, got, want)
}
func TestContract_UnifiedActionLifecycleEventsJSONSnapshot(t *testing.T) {
now := time.Date(2026, 3, 18, 16, 0, 0, 0, time.UTC)
payload := struct {
ActionID string `json:"actionId"`
Events []unifiedresources.ActionLifecycleEvent `json:"events"`
Count int `json:"count"`
}{
ActionID: "action-1",
Events: []unifiedresources.ActionLifecycleEvent{
{
ActionID: "action-1",
Timestamp: now,
State: unifiedresources.ActionStatePlanned,
Actor: "system",
Message: "planned",
},
},
Count: 1,
}
got, err := json.Marshal(payload)
if err != nil {
t.Fatalf("marshal action lifecycle response: %v", err)
}
const want = `{
"actionId":"action-1",
"events":[
{
"actionId":"action-1",
"timestamp":"2026-03-18T16:00:00Z",
"state":"planned",
"actor":"system",
"message":"planned"
}
],
"count":1
}`
assertJSONSnapshot(t, got, want)
}
func TestContract_UnifiedExportAuditsJSONSnapshot(t *testing.T) {
now := time.Date(2026, 3, 18, 16, 0, 0, 0, time.UTC)
payload := struct {
Audits []unifiedresources.ExportAuditRecord `json:"audits"`
Count int `json:"count"`
}{
Audits: []unifiedresources.ExportAuditRecord{
{
ID: "export-1",
Timestamp: now,
Actor: "agent:ops",
EnvelopeHash: "hash-1",
Decision: unifiedresources.ExportRedacted,
Destination: "local-llama",
Redactions: []string{"metadata.hostname"},
},
},
Count: 1,
}
got, err := json.Marshal(payload)
if err != nil {
t.Fatalf("marshal export audits response: %v", err)
}
const want = `{
"audits":[
{
"id":"export-1",
"timestamp":"2026-03-18T16:00:00Z",
"actor":"agent:ops",
"envelopeHash":"hash-1",
"decision":"redacted",
"destination":"local-llama",
"redactions":["metadata.hostname"]
}
],
"count":1
}`
assertJSONSnapshot(t, got, want)
}
func TestContract_UnifiedAuditLimitCapsOversizedRequests(t *testing.T) {
if got := parseAuditLimit("5000", 100); got != 1000 {
t.Fatalf("parseAuditLimit oversized request = %d, want 1000", got)
}
if got := parseAuditLimit("250", 100); got != 250 {
t.Fatalf("parseAuditLimit normal request = %d, want 250", got)
}
}
func TestContract_AuditStoreReadErrorsUseStructuredAPIErrorCodes(t *testing.T) {
cases := []struct {
name string
err error
wantStatus int
wantCode string
wantRetryAfter bool
}{
{
name: "busy",
err: fmt.Errorf("failed to query audit events: %w", fmt.Errorf("database is locked (5) (SQLITE_BUSY)")),
wantStatus: http.StatusServiceUnavailable,
wantCode: "audit_store_busy",
wantRetryAfter: true,
},
{
name: "unavailable",
err: fmt.Errorf("failed to query audit events: %w", fmt.Errorf("no such table: audit_events")),
wantStatus: http.StatusServiceUnavailable,
wantCode: "audit_store_unavailable",
},
{
name: "query_failed",
err: fmt.Errorf("failed to query audit events: %w", fmt.Errorf("unexpected query failure")),
wantStatus: http.StatusInternalServerError,
wantCode: "query_failed",
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
if tc.wantCode == "audit_store_busy" && !audit.IsStoreBusyError(tc.err) {
t.Fatal("busy fixture must satisfy audit store busy classification")
}
if tc.wantCode == "audit_store_unavailable" && !audit.IsStoreUnavailableError(tc.err) {
t.Fatal("unavailable fixture must satisfy audit store unavailable classification")
}
rec := httptest.NewRecorder()
writeAuditReadErrorResponse(rec, tc.err, "Failed to query audit events")
if rec.Code != tc.wantStatus {
t.Fatalf("status = %d, want %d", rec.Code, tc.wantStatus)
}
if got := rec.Header().Get("Retry-After"); tc.wantRetryAfter && got == "" {
t.Fatal("busy audit-store errors must include Retry-After")
} else if !tc.wantRetryAfter && got != "" {
t.Fatalf("Retry-After = %q, want empty", got)
}
var payload APIError
if err := json.Unmarshal(rec.Body.Bytes(), &payload); err != nil {
t.Fatalf("decode APIError: %v", err)
}
if payload.Code != tc.wantCode {
t.Fatalf("code = %q, want %q", payload.Code, tc.wantCode)
}
if payload.StatusCode != tc.wantStatus {
t.Fatalf("status_code = %d, want %d", payload.StatusCode, tc.wantStatus)
}
})
}
}
func TestContract_EmbeddedFrontendWarningUsesCanonicalDevEntrypoints(t *testing.T) {
path := filepath.Join("DO_NOT_EDIT_FRONTEND_HERE.md")
body, err := os.ReadFile(path)
if err != nil {
t.Fatalf("read embedded frontend warning: %v", err)
}
text := string(body)
if !strings.Contains(text, "http://127.0.0.1:5173") {
t.Fatalf("embedded frontend warning must point to the frontend dev shell on 5173")
}
if !strings.Contains(text, "http://127.0.0.1:7655") {
t.Fatalf("embedded frontend warning must identify the backend on 7655")
}
if strings.Contains(text, "The dev server (port 7655) will hot-reload") {
t.Fatalf("embedded frontend warning must not describe 7655 as the hot-reload dev server")
}
}
func TestContract_ShippedSecurityDocReferencesStayLocal(t *testing.T) {
if shippedSecurityDocPath != "/docs/SECURITY.md" {
t.Fatalf("expected shipped security doc path, got %q", shippedSecurityDocPath)
}
if shippedSecurityContainerNoticeDocAnchor != "/docs/SECURITY.md#critical-security-notice-for-container-deployments" {
t.Fatalf("expected shipped security container notice path, got %q", shippedSecurityContainerNoticeDocAnchor)
}
}
// TestContract_ConnectionPayloadShapeStaysCanonical pins the JSON shape of
// the unified connections ledger so the Go types in
// `internal/api/connections_types.go` stay in lockstep with the
// `frontend-modern/src/api/connections.ts` client. Adding or renaming fields
// on this wire shape requires an explicit update to both ends in the same
// commit.
func TestContract_ConnectionPayloadShapeStaysCanonical(t *testing.T) {
conn := Connection{
ID: "pve-lab",
Type: ConnectionTypePVE,
Name: "lab",
Address: "https://pve.lab:8006",
State: ConnectionStateActive,
StateReason: "",
Enabled: true,
Surfaces: []string{"vms", "containers"},
Scope: map[string]bool{"vms": true, "containers": true},
LastSeen: timePtr(time.Date(2026, 4, 19, 10, 0, 0, 0, time.UTC)),
LastError: nil,
Source: ConnectionSourceManual,
Fleet: ConnectionFleetGovernance{
EnrollmentState: fleetStateConfigured,
LivenessState: fleetStateActive,
VersionDrift: fleetStateNotApplicable,
AdapterHealth: fleetStateHealthy,
ConfigRollout: fleetStateConfigured,
CredentialStatus: fleetStateVerified,
UpdateStatus: fleetStateNotApplicable,
RemoteControl: fleetStateNotApplicable,
},
Capabilities: ConnectionCapabilities{SupportsPause: true, SupportsScope: true, SupportsTest: true},
}
system := ConnectionSystem{
ID: "pve-lab",
Type: ConnectionTypePVE,
ClusterName: "homelab",
Components: []ConnectionSystemComponent{
{
ConnectionID: "pve-lab",
Type: ConnectionTypePVE,
Role: ConnectionSystemComponentRolePrimary,
},
},
}
body, err := json.Marshal(ConnectionsListResponse{
Connections: []Connection{conn},
Systems: []ConnectionSystem{system},
})
if err != nil {
t.Fatalf("marshal Connection: %v", err)
}
want := `{"connections":[{"id":"pve-lab","type":"pve","name":"lab","address":"https://pve.lab:8006","state":"active","enabled":true,"surfaces":["vms","containers"],"scope":{"containers":true,"vms":true},"lastSeen":"2026-04-19T10:00:00Z","source":"manual","fleet":{"enrollmentState":"configured","livenessState":"active","versionDrift":"not-applicable","adapterHealth":"healthy","configRollout":"configured","credentialStatus":"verified","updateStatus":"not-applicable","remoteControl":"not-applicable"},"capabilities":{"supportsPause":true,"supportsScope":true,"supportsTest":true}}],"systems":[{"id":"pve-lab","type":"pve","clusterName":"homelab","components":[{"connectionId":"pve-lab","type":"pve","role":"primary"}]}]}`
assertJSONSnapshot(t, body, want)
}
func TestContract_ConnectionSystemMembersPayloadShapeStaysCanonical(t *testing.T) {
system := ConnectionSystem{
ID: "pve-lab",
Type: ConnectionTypePVE,
ClusterName: "homelab",
Components: []ConnectionSystemComponent{
{
ConnectionID: "pve-lab",
Type: ConnectionTypePVE,
Role: ConnectionSystemComponentRolePrimary,
},
{
ConnectionID: "agent:agent-lab",
Type: ConnectionTypeAgent,
Role: ConnectionSystemComponentRoleAttachment,
},
},
Members: []ConnectionSystemMember{
{
ID: "node-lab",
Name: "lab",
Endpoint: "https://lab:8006",
HostAliases: []string{"lab", "192.168.0.2"},
State: ConnectionStateActive,
LastSeen: timePtr(time.Date(2026, 4, 23, 12, 0, 0, 0, time.UTC)),
Primary: true,
AgentConnectionID: "agent:agent-lab",
},
{
ID: "node-minipc",
Name: "minipc",
Endpoint: "https://minipc:8006",
HostAliases: []string{"minipc"},
State: ConnectionStateStale,
LastSeen: timePtr(time.Date(2026, 4, 23, 11, 55, 0, 0, time.UTC)),
},
},
}
body, err := json.Marshal(system)
if err != nil {
t.Fatalf("marshal ConnectionSystem with members: %v", err)
}
want := `{"id":"pve-lab","type":"pve","clusterName":"homelab","components":[{"connectionId":"pve-lab","type":"pve","role":"primary"},{"connectionId":"agent:agent-lab","type":"agent","role":"attachment"}],"members":[{"id":"node-lab","name":"lab","endpoint":"https://lab:8006","hostAliases":["lab","192.168.0.2"],"state":"active","lastSeen":"2026-04-23T12:00:00Z","primary":true,"agentConnectionId":"agent:agent-lab"},{"id":"node-minipc","name":"minipc","endpoint":"https://minipc:8006","hostAliases":["minipc"],"state":"stale","lastSeen":"2026-04-23T11:55:00Z"}]}`
assertJSONSnapshot(t, body, want)
}
func TestContract_ConnectionSystemHostAgentAttachmentPayloadShapeStaysCanonical(t *testing.T) {
response := ConnectionsListResponse{
Connections: []Connection{
{
ID: "pve:delly",
Type: ConnectionTypePVE,
Name: "delly",
Address: "https://delly:8006",
HostAliases: []string{"delly"},
State: ConnectionStateUnauthorized,
Enabled: true,
Surfaces: []string{"vms", "containers", "storage", "backups"},
Scope: map[string]bool{"vms": true, "containers": true, "storage": true, "backups": true},
Source: ConnectionSourceAgent,
Fleet: ConnectionFleetGovernance{
EnrollmentState: fleetStateConfigured,
LivenessState: string(ConnectionStateUnauthorized),
VersionDrift: fleetStateNotApplicable,
AdapterHealth: fleetStateBlocked,
ConfigRollout: fleetStateConfigured,
CredentialStatus: fleetStateInvalid,
UpdateStatus: fleetStateNotApplicable,
RemoteControl: fleetStateNotApplicable,
},
Capabilities: ConnectionCapabilities{SupportsPause: true, SupportsScope: true, SupportsTest: true},
},
{
ID: "agent:agent-delly",
Type: ConnectionTypeAgent,
Name: "delly",
Address: "delly",
HostAliases: []string{"delly", "192.168.0.5"},
State: ConnectionStateActive,
Enabled: true,
Surfaces: []string{"host"},
Scope: map[string]bool{"host": true},
LastSeen: timePtr(time.Date(2026, 5, 13, 23, 45, 0, 0, time.UTC)),
Source: ConnectionSourceAgent,
Fleet: ConnectionFleetGovernance{
EnrollmentState: fleetStateEnrolled,
LivenessState: fleetStateActive,
VersionDrift: fleetStateUnknown,
AdapterHealth: fleetStateHealthy,
ConfigRollout: fleetStateReported,
CredentialStatus: fleetStateVerified,
UpdateStatus: fleetStateUnknown,
RemoteControl: fleetStateDisabled,
},
Capabilities: ConnectionCapabilities{SupportsPause: false, SupportsScope: false, SupportsTest: false},
},
},
Systems: []ConnectionSystem{
{
ID: "pve:delly",
Type: ConnectionTypePVE,
Components: []ConnectionSystemComponent{
{
ConnectionID: "pve:delly",
Type: ConnectionTypePVE,
Role: ConnectionSystemComponentRolePrimary,
},
{
ConnectionID: "agent:agent-delly",
Type: ConnectionTypeAgent,
Role: ConnectionSystemComponentRoleAttachment,
},
},
},
},
}
body, err := json.Marshal(response)
if err != nil {
t.Fatalf("marshal ConnectionsListResponse with direct host agent attachment: %v", err)
}
want := `{"connections":[{"id":"pve:delly","type":"pve","name":"delly","address":"https://delly:8006","hostAliases":["delly"],"state":"unauthorized","enabled":true,"surfaces":["vms","containers","storage","backups"],"scope":{"backups":true,"containers":true,"storage":true,"vms":true},"source":"agent","fleet":{"enrollmentState":"configured","livenessState":"unauthorized","versionDrift":"not-applicable","adapterHealth":"blocked","configRollout":"configured","credentialStatus":"invalid","updateStatus":"not-applicable","remoteControl":"not-applicable"},"capabilities":{"supportsPause":true,"supportsScope":true,"supportsTest":true}},{"id":"agent:agent-delly","type":"agent","name":"delly","address":"delly","hostAliases":["delly","192.168.0.5"],"state":"active","enabled":true,"surfaces":["host"],"scope":{"host":true},"lastSeen":"2026-05-13T23:45:00Z","source":"agent","fleet":{"enrollmentState":"enrolled","livenessState":"active","versionDrift":"unknown","adapterHealth":"healthy","configRollout":"reported","credentialStatus":"verified","updateStatus":"unknown","remoteControl":"disabled"},"capabilities":{"supportsPause":false,"supportsScope":false,"supportsTest":false}}],"systems":[{"id":"pve:delly","type":"pve","components":[{"connectionId":"pve:delly","type":"pve","role":"primary"},{"connectionId":"agent:agent-delly","type":"agent","role":"attachment"}]}]}`
assertJSONSnapshot(t, body, want)
}
func TestContract_AgentConnectionPayloadIncludesVersionFields(t *testing.T) {
conn := Connection{
ID: "agent:host-1",
Type: ConnectionTypeAgent,
Name: "host-1",
Address: "host-1",
HostAliases: []string{"host-1", "192.168.0.2"},
State: ConnectionStateActive,
Enabled: true,
Surfaces: []string{"host"},
Scope: map[string]bool{"host": true},
LastSeen: timePtr(time.Date(2026, 4, 22, 12, 0, 0, 0, time.UTC)),
Source: ConnectionSourceAgent,
AgentIdentity: &ConnectionAgentIdentity{
Hostname: "host-1",
Platform: "linux",
HostProfile: "unraid",
OSName: "Unraid",
OSVersion: "7.1.0",
KernelVersion: "6.12.0",
Architecture: "x86_64",
ReportIP: "192.168.0.2",
CommandsEnabled: true,
},
AgentVersion: "6.0.0",
ExpectedAgentVersion: "6.0.2",
AgentUpdateAvailable: true,
Fleet: ConnectionFleetGovernance{
EnrollmentState: fleetStateEnrolled,
LivenessState: fleetStateActive,
VersionDrift: fleetStateBehind,
AdapterHealth: fleetStateHealthy,
ConfigRollout: fleetStateReported,
CredentialStatus: fleetStateVerified,
UpdateStatus: fleetStateUpdateAvailable,
RemoteControl: fleetStateEnabled,
CommandPolicy: &ConnectionFleetCommandPolicy{
Status: fleetStateEnabled,
Desired: fleetStateEnabled,
Applied: fleetStateEnabled,
Enforcement: fleetCommandPolicyInSync,
Reason: "agent command execution matches the desired enabled policy",
},
},
Capabilities: ConnectionCapabilities{
SupportsPause: false,
SupportsScope: false,
SupportsTest: false,
},
}
body, err := json.Marshal(conn)
if err != nil {
t.Fatalf("marshal agent Connection: %v", err)
}
if conn.AgentIdentity.Platform == conn.AgentIdentity.HostProfile {
t.Fatalf("agent platform must remain runtime platform, got profile id %q", conn.AgentIdentity.Platform)
}
want := `{"id":"agent:host-1","type":"agent","name":"host-1","address":"host-1","hostAliases":["host-1","192.168.0.2"],"state":"active","enabled":true,"surfaces":["host"],"scope":{"host":true},"lastSeen":"2026-04-22T12:00:00Z","source":"agent","agentIdentity":{"hostname":"host-1","platform":"linux","hostProfile":"unraid","osName":"Unraid","osVersion":"7.1.0","kernelVersion":"6.12.0","architecture":"x86_64","reportIp":"192.168.0.2","commandsEnabled":true},"agentVersion":"6.0.0","expectedAgentVersion":"6.0.2","agentUpdateAvailable":true,"fleet":{"enrollmentState":"enrolled","livenessState":"active","versionDrift":"behind","adapterHealth":"healthy","configRollout":"reported","credentialStatus":"verified","updateStatus":"update-available","remoteControl":"enabled","commandPolicy":{"status":"enabled","desired":"enabled","applied":"enabled","enforcement":"in-sync","reason":"agent command execution matches the desired enabled policy"}},"capabilities":{"supportsPause":false,"supportsScope":false,"supportsTest":false}}`
assertJSONSnapshot(t, body, want)
}
func TestContract_AgentDefaultDesiredConfigDoesNotCreateRolloutAttention(t *testing.T) {
now := time.Date(2026, 5, 14, 10, 30, 0, 0, time.UTC)
connections := buildConnections(aggregatorInputs{
hosts: []models.Host{
{
ID: "host-1",
Hostname: "host-1",
ReportIP: "192.0.2.42",
LastSeen: now.Add(-10 * time.Second),
AgentVersion: "6.0.0",
Platform: "linux",
CommandsEnabled: false,
TokenID: "token-1",
},
},
agentDesiredConfigs: map[string]connectionAgentDesiredConfig{
"host-1": {},
},
expectedAgentVersion: "6.0.0",
now: now,
})
if len(connections) != 1 {
t.Fatalf("expected one agent connection, got %d", len(connections))
}
body, err := json.Marshal(connections[0])
if err != nil {
t.Fatalf("marshal agent Connection: %v", err)
}
want := `{"id":"agent:host-1","type":"agent","name":"host-1","address":"host-1","hostAliases":["host-1","192.0.2.42"],"state":"active","enabled":true,"surfaces":["host"],"scope":{"host":true},"lastSeen":"2026-05-14T10:29:50Z","source":"agent","agentIdentity":{"hostname":"host-1","platform":"linux","reportIp":"192.0.2.42"},"agentVersion":"6.0.0","expectedAgentVersion":"6.0.0","fleet":{"enrollmentState":"enrolled","livenessState":"active","versionDrift":"current","adapterHealth":"healthy","configRollout":"reported","credentialStatus":"verified","updateStatus":"current","remoteControl":"disabled","configDrift":{"status":"not-applicable","lastObservedAt":"2026-05-14T10:29:50Z","reason":"no managed agent configuration override is assigned"},"rollout":{"status":"current","stage":"applied","reason":"no managed agent configuration rollout is assigned"},"credentialHealth":{"status":"verified","kind":"agent-token","rotation":"healthy","lastVerifiedAt":"2026-05-14T10:29:50Z"},"commandPolicy":{"status":"disabled","desired":"unknown","applied":"disabled","enforcement":"not-applicable","reason":"no desired command-policy override is configured; reporting the agent-applied state"}},"capabilities":{"supportsPause":false,"supportsScope":false,"supportsTest":false}}`
assertJSONSnapshot(t, body, want)
}
func TestContract_ConnectionsListIncludesAgentHostsFromUnifiedReadState(t *testing.T) {
cfg := &config.Config{DataPath: t.TempDir()}
monitor, err := monitoring.New(cfg)
if err != nil {
t.Fatalf("monitoring.New: %v", err)
}
t.Cleanup(func() { monitor.Stop() })
adapter := unifiedresources.NewMonitorAdapter(nil)
setTestUnexportedField(t, monitor, "resourceStore", monitoring.ResourceStoreInterface(adapter))
now := time.Now().UTC()
adapter.PopulateSupplementalRecords(unifiedresources.SourceAgent, []unifiedresources.IngestRecord{
{
SourceID: "agent-1",
Resource: unifiedresources.Resource{
ID: "resource-host-1",
Type: unifiedresources.ResourceTypeAgent,
Name: "mini-pc",
Status: unifiedresources.StatusOnline,
LastSeen: now,
Agent: &unifiedresources.AgentData{
AgentID: "agent-1",
Hostname: "mini-pc",
MachineID: "machine-1",
},
Identity: unifiedresources.ResourceIdentity{
MachineID: "machine-1",
Hostnames: []string{"mini-pc"},
},
},
},
})
if got := len(monitorState(t, monitor).GetHosts()); got != 0 {
t.Fatalf("expected legacy host snapshot to stay empty, got %d hosts", got)
}
if got := len(monitor.GetUnifiedReadStateOrSnapshot().Hosts()); got != 1 {
t.Fatalf("expected unified read-state to expose 1 agent host, got %d", got)
}
handler := NewConnectionsHandlers(
func(context.Context) *config.Config { return cfg },
func(context.Context) *config.ConfigPersistence { return nil },
func(context.Context) *monitoring.Monitor { return monitor },
)
req := httptest.NewRequest(http.MethodGet, "/api/connections", nil)
rec := httptest.NewRecorder()
handler.HandleList(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("status = %d, body=%s", rec.Code, rec.Body.String())
}
var resp ConnectionsListResponse
if err := json.NewDecoder(rec.Body).Decode(&resp); err != nil {
t.Fatalf("decode connections response: %v", err)
}
if len(resp.Connections) != 1 {
t.Fatalf("expected 1 connection from unified read-state, got %d", len(resp.Connections))
}
if len(resp.Systems) != 1 {
t.Fatalf("expected 1 grouped system from unified read-state, got %d", len(resp.Systems))
}
conn := resp.Connections[0]
if conn.ID != "agent:agent-1" {
t.Fatalf("connection id = %q, want %q", conn.ID, "agent:agent-1")
}
if conn.Type != ConnectionTypeAgent {
t.Fatalf("connection type = %q, want %q", conn.Type, ConnectionTypeAgent)
}
if conn.Name != "mini-pc" || conn.Address != "mini-pc" {
t.Fatalf("unexpected agent identity: name=%q address=%q", conn.Name, conn.Address)
}
if conn.State != ConnectionStateActive {
t.Fatalf("connection state = %q, want %q", conn.State, ConnectionStateActive)
}
if conn.Source != ConnectionSourceAgent {
t.Fatalf("connection source = %q, want %q", conn.Source, ConnectionSourceAgent)
}
if resp.Systems[0].ID != "agent:agent-1" || resp.Systems[0].Type != ConnectionTypeAgent {
t.Fatalf("unexpected grouped system projection: %+v", resp.Systems[0])
}
}
// TestContract_ProbePayloadShapeStaysCanonical pins the POST
// /api/connections/probe wire shape. Hint keys are free-form; the envelope
// fields (type, host, port, hints) are the contract boundary.
func TestContract_ProbePayloadShapeStaysCanonical(t *testing.T) {
resp := ProbeResponse{
Candidates: []ProbeCandidate{
{
Type: ConnectionTypePVE,
Host: "https://pve.lab:8006",
Port: 8006,
Hints: map[string]string{"product": "Proxmox VE", "version": "8.2.4"},
},
},
ProbedMs: 812,
}
body, err := json.Marshal(resp)
if err != nil {
t.Fatalf("marshal ProbeResponse: %v", err)
}
want := `{"candidates":[{"type":"pve","host":"https://pve.lab:8006","port":8006,"hints":{"product":"Proxmox VE","version":"8.2.4"}}],"probedMs":812}`
assertJSONSnapshot(t, body, want)
}
func TestContract_ProbeRejectsBlockedMetadataAddress(t *testing.T) {
handler := NewConnectionsHandlers(nil, nil, nil)
req := httptest.NewRequest(http.MethodPost, "/api/connections/probe", strings.NewReader(`{"address":"169.254.169.254"}`))
req.Header.Set("Content-Type", "application/json")
rec := httptest.NewRecorder()
handler.HandleProbe(rec, req)
if rec.Code != http.StatusBadRequest {
t.Fatalf("status = %d, want %d (%s)", rec.Code, http.StatusBadRequest, rec.Body.String())
}
var payload APIError
if err := json.NewDecoder(rec.Body).Decode(&payload); err != nil {
t.Fatalf("decode error payload: %v", err)
}
if payload.Code != "invalid_address" {
t.Fatalf("code = %q, want %q", payload.Code, "invalid_address")
}
if !strings.Contains(payload.ErrorMessage, "metadata service address is not allowed") {
t.Fatalf("error = %q, want metadata-service rejection", payload.ErrorMessage)
}
}
func TestContract_SimpleStatsUsesTextNodesForContainerFields(t *testing.T) {
req := httptest.NewRequest(http.MethodGet, "/simple-stats", nil)
rec := httptest.NewRecorder()
var router Router
router.handleSimpleStats(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("status = %d, want %d", rec.Code, http.StatusOK)
}
body := rec.Body.String()
if strings.Contains(body, "row.innerHTML") {
t.Fatalf("simple stats page must not use row.innerHTML for container rendering")
}
if !strings.Contains(body, "textContent") {
t.Fatalf("simple stats page must use textContent for container rendering")
}
}
func TestContract_APIRejectsUnsafeIncomingRequestIDHeader(t *testing.T) {
handler := ErrorHandler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusNoContent)
}))
req := httptest.NewRequest(http.MethodGet, "/api/health", nil)
req.Header.Set("X-Request-ID", "bad\nrequest-id")
rec := httptest.NewRecorder()
handler.ServeHTTP(rec, req)
got := rec.Header().Get("X-Request-ID")
if got == "" {
t.Fatal("expected generated request id")
}
if got == "bad\nrequest-id" {
t.Fatalf("unsafe request id header must not round-trip: %q", got)
}
if strings.ContainsAny(got, "\r\n") {
t.Fatalf("response request id must not contain control characters: %q", got)
}
}
func TestContract_BootstrapTokenValidationRateLimitsPerClient(t *testing.T) {
dataDir := t.TempDir()
router := &Router{
config: &config.Config{
DataPath: dataDir,
ConfigPath: dataDir,
},
bootstrapTokenValidationLimiter: NewRateLimiter(1, time.Hour),
}
t.Cleanup(router.bootstrapTokenValidationLimiter.Stop)
router.initializeBootstrapToken()
req := httptest.NewRequest(http.MethodPost, "/api/security/validate-bootstrap-token", strings.NewReader(`{"token":"deadbeef"}`))
req.RemoteAddr = "127.0.0.1:1234"
rec := httptest.NewRecorder()
router.handleValidateBootstrapToken(rec, req)
if rec.Code != http.StatusUnauthorized {
t.Fatalf("status = %d, want %d", rec.Code, http.StatusUnauthorized)
}
req = httptest.NewRequest(http.MethodPost, "/api/security/validate-bootstrap-token", strings.NewReader(`{"token":"deadbeef"}`))
req.RemoteAddr = "127.0.0.1:1234"
rec = httptest.NewRecorder()
router.handleValidateBootstrapToken(rec, req)
if rec.Code != http.StatusTooManyRequests {
t.Fatalf("status = %d, want %d (%s)", rec.Code, http.StatusTooManyRequests, rec.Body.String())
}
if retryAfter := rec.Header().Get("Retry-After"); retryAfter == "" {
t.Fatal("expected Retry-After header on bootstrap token validation rate limit")
}
}
func TestContract_DeployHandlersDoNotSurfaceLicenseSlotCapacityCopy(t *testing.T) {
source, err := os.ReadFile("deploy_handlers.go")
if err != nil {
t.Fatalf("read deploy handlers: %v", err)
}
text := string(source)
if strings.Contains(text, "No license slots available") {
t.Fatal("deploy retry capacity denial must not surface legacy license-slot copy")
}
if strings.Contains(text, "reservedLicenseSlots") {
t.Fatal("deploy response must not expose retired reservedLicenseSlots field")
}
}
func mustStreamEvent(t *testing.T, eventType string, data interface{}) chat.StreamEvent {
t.Helper()
raw, err := json.Marshal(data)
if err != nil {
t.Fatalf("marshal stream data: %v", err)
}
return chat.StreamEvent{
Type: eventType,
Data: raw,
}
}
func assertJSONSnapshot(t *testing.T, got []byte, want string, excludeKeys ...string) {
t.Helper()
// excludeKeys lets callers strip top-level fields whose values are
// dynamic per run (timestamps, durations) before snapshot comparison.
// The structural shape of the response is still validated; only the
// listed keys are removed from both `got` and `want`.
gotBytes := got
wantBytes := []byte(want)
if len(excludeKeys) > 0 {
var err error
gotBytes, err = stripJSONTopLevelKeys(got, excludeKeys)
if err != nil {
t.Fatalf("strip got json: %v", err)
}
wantBytes, err = stripJSONTopLevelKeys([]byte(want), excludeKeys)
if err != nil {
t.Fatalf("strip want json: %v", err)
}
}
var gotCompact bytes.Buffer
var wantCompact bytes.Buffer
if err := json.Compact(&gotCompact, gotBytes); err != nil {
t.Fatalf("compact got json: %v", err)
}
if err := json.Compact(&wantCompact, wantBytes); err != nil {
t.Fatalf("compact want json: %v", err)
}
if gotCompact.String() != wantCompact.String() {
t.Fatalf("json snapshot mismatch\nwant: %s\ngot: %s", wantCompact.String(), gotCompact.String())
}
}
// stripJSONTopLevelKeys removes the named top-level keys from a JSON object
// and re-serializes. Used by assertJSONSnapshot to ignore dynamic fields.
func stripJSONTopLevelKeys(data []byte, keys []string) ([]byte, error) {
var obj map[string]interface{}
if err := json.Unmarshal(data, &obj); err != nil {
return nil, err
}
for _, k := range keys {
delete(obj, k)
}
return json.Marshal(obj)
}
// TestContract_InvestigationProjectionCarriesNeverAutoRemediate pins
// the cross-path agreement: the projection-builder closure in
// router.go must populate NeverAutoRemediate on the projection so
// the investigation read path sees the same lock-against-remediation
// flag the action broker enforces. Without this, Patrol could
// propose fixes on locked resources that the broker would refuse —
// the fix proposal shouldn't have happened in the first place.
func TestContract_InvestigationProjectionCarriesNeverAutoRemediate(t *testing.T) {
source, err := os.ReadFile("router.go")
if err != nil {
t.Fatalf("read router.go: %v", err)
}
src := string(source)
if !strings.Contains(src, "NeverAutoRemediate: state.NeverAutoRemediate,") {
t.Error("router.go must populate NeverAutoRemediate on the projection so the investigation read path sees the operator's lock — drift here means Patrol can propose fixes the broker refuses")
}
}
// TestContract_AgentEventsStreamPublishesOnFindingCreated pins the
// agent SSE stream's most consequential producer hook: the
// findings-runtime callback in router.go must publish a
// finding.created event when a finding is new AND not auto-dismissed
// by operator-state suppression. Without this guard, every
// patrol-cycle re-detection would fire an event (noise) and findings
// the operator already silenced would still notify (contradicting
// slice 31/32 suppression semantics).
func TestContract_AgentEventsStreamPublishesOnFindingCreated(t *testing.T) {
source, err := os.ReadFile("router.go")
if err != nil {
t.Fatalf("read router.go: %v", err)
}
src := string(source)
if !strings.Contains(src, "PublishFindingCreated(AgentEventFindingCreatedPayload{") {
t.Error("router.go must publish finding.created events to the agent stream when a new finding is added")
}
if !strings.Contains(src, "isNew && r.agentEventBroadcaster != nil && f.DismissedReason == \"\"") {
t.Error("publish gate must fire only when isNew, broadcaster wired, and finding not auto-dismissed by operator-state — otherwise the stream contradicts the suppression contract")
}
}
// TestContract_AgentEventsStreamPublishesOnApprovalPending pins the
// agent SSE stream's governance-aware producer hook: the approval
// store's post-create callback must publish an approval.pending
// event so an agent holding /api/agent/events open hears about new
// pending approvals in real time without polling. The callback is
// installed via AIHandler.SetApprovalCreatedCallback so it survives
// the AIHandler's lazy approval-store rebuilds. Drift here means
// the only signal an agent has that an operator decision is needed
// is poll-based — losing the substrate's push-notification
// guarantee for governance state.
func TestContract_AgentEventsStreamPublishesOnApprovalPending(t *testing.T) {
source, err := os.ReadFile("router.go")
if err != nil {
t.Fatalf("read router.go: %v", err)
}
src := string(source)
if !strings.Contains(src, "r.aiHandler.SetApprovalCreatedCallback(") {
t.Error("router.go must install the approval-created callback on AIHandler so the agent SSE stream is wired before the first approval lands")
}
if !strings.Contains(src, "broadcaster.PublishApprovalPending(AgentEventApprovalPendingPayload{") {
t.Error("router.go must bridge approval creation into PublishApprovalPending — drift means agents lose the push-notification path for governance state")
}
if !strings.Contains(src, "ApprovalID: req.ID") {
t.Error("approval.pending payload must carry the approval id so agents can correlate the event with /api/approvals/{id}")
}
if !strings.Contains(src, "ResourceID: req.CanonicalResourceID()") {
t.Error("approval.pending payload must derive the canonical resourceId via CanonicalResourceID so agents can match it against the rest of Pulse")
}
}
// TestContract_ApprovalStorePostCreateCallback pins the seam the
// SSE bridge depends on. The approval store must expose a
// SetOnApprovalCreated setter and CreateApproval must dispatch the
// callback on its own goroutine after the approval is persisted.
// Removing or in-lining the callback breaks the bridge silently.
func TestContract_ApprovalStorePostCreateCallback(t *testing.T) {
source, err := os.ReadFile("../ai/approval/store.go")
if err != nil {
t.Fatalf("read approval/store.go: %v", err)
}
src := string(source)
if !strings.Contains(src, "func (s *Store) SetOnApprovalCreated(cb func(*ApprovalRequest))") {
t.Error("approval store must expose SetOnApprovalCreated so the API layer can bridge approval creation into the agent SSE stream")
}
if !strings.Contains(src, "if cb := s.onApprovalCreated; cb != nil {") {
t.Error("CreateApproval must check the post-create callback under lock and dispatch it; drift here drops approval.pending events")
}
if !strings.Contains(src, "go cb(&snapshot)") {
t.Error("post-create callback must run on its own goroutine to keep the approval hot path off any consumer's slowness")
}
}
// TestContract_AgentEventApprovalPendingKindIsStable pins the
// wire-stable event kind string. Renaming AgentEventApprovalPending
// breaks every agent that branches on the SSE event-type field; the
// constant is part of the contract, not an implementation detail.
func TestContract_AgentEventApprovalPendingKindIsStable(t *testing.T) {
if AgentEventApprovalPending != AgentEventKind(agentcapabilities.EventKindApprovalPending) {
t.Errorf("AgentEventApprovalPending = %q, want shared event kind %q",
AgentEventApprovalPending, agentcapabilities.EventKindApprovalPending)
}
if string(AgentEventApprovalPending) != "approval.pending" {
t.Errorf("AgentEventApprovalPending wire value = %q, want approval.pending", AgentEventApprovalPending)
}
}
// TestContract_SubscribeEventsCapabilityListsApprovalPending pins
// the discovery contract: the capabilities manifest must mention
// approval.pending under subscribe_events so an agent reading the
// manifest learns the kind exists without out-of-band documentation.
// Drift here means external agents miss the event entirely because
// they didn't know to listen for it.
func TestContract_SubscribeEventsCapabilityListsApprovalPending(t *testing.T) {
capability, ok := agentcapabilities.FindCapability(agentcapabilities.CanonicalManifest().Capabilities, agentcapabilities.EventSubscriptionCapabilityName)
if !ok {
t.Fatal("canonical manifest must include subscribe_events")
}
if !strings.Contains(capability.Description, string(agentcapabilities.EventKindApprovalPending)+" when a remediation request enters StatusPending") {
t.Error("subscribe_events description must mention approval.pending so agents discover the kind through the manifest")
}
}
// TestContract_AgentEventsHeartbeatIsStreamLocal pins that
// heartbeat keepalives are written to the connected SSE response
// rather than published through the broadcaster. Heartbeats are
// connection health signals, not product events; publishing them
// globally makes N subscribers create N heartbeat events that fan
// out to every other subscriber.
func TestContract_AgentEventsHeartbeatIsStreamLocal(t *testing.T) {
source, err := os.ReadFile("agent_events.go")
if err != nil {
t.Fatalf("read agent_events.go: %v", err)
}
src := string(source)
if !strings.Contains(src, "writeAgentSSEEvent(w, b.stampEvent(AgentEvent{Kind: AgentEventHeartbeat}))") {
t.Error("HandleAgentEvents must write heartbeat events directly to the connected response")
}
if strings.Contains(src, "case <-heartbeat.C:\n\t\t\tb.Publish(AgentEvent{Kind: AgentEventHeartbeat})") {
t.Error("HandleAgentEvents must not publish heartbeat events through the broadcaster")
}
}
// TestContract_AgentEventsStreamPublishesOnActionCompleted pins the
// agent SSE stream's third producer hook: the executor's
// post-completion callback must publish action.completed events for
// every terminal-state audit so an agent holding /api/agent/events
// open closes the dispatch loop without polling. The bridge lives in
// wireAIChatDependenciesForService so the callback is re-installed
// on every per-org chat-service init. Drift here means agents lose
// the push-notification path for dispatch outcomes — including the
// refused-before-dispatch failures that carry stable error tokens.
func TestContract_AgentEventsStreamPublishesOnActionCompleted(t *testing.T) {
source, err := os.ReadFile("router.go")
if err != nil {
t.Fatalf("read router.go: %v", err)
}
src := string(source)
if !strings.Contains(src, "executor.SetOnActionCompleted(func(record unifiedresources.ActionAuditRecord)") {
t.Error("router.go must install the post-completion callback on the executor returned from chatService.GetExecutor() so the agent SSE stream is wired per-org")
}
if !strings.Contains(src, "broadcaster.PublishActionCompletedRecord(record)") {
t.Error("router.go must bridge executor action completion into PublishActionCompletedRecord — drift means agents lose the push-notification path for dispatch outcomes")
}
if !strings.Contains(src, "r.resourceHandlers.SetActionCompletedPublisher(r.agentEventBroadcaster.PublishActionCompletedRecord)") {
t.Error("router.go must also bridge API-owned action execution completions into the agent SSE stream")
}
eventsSource, err := os.ReadFile("agent_events.go")
if err != nil {
t.Fatalf("read agent_events.go: %v", err)
}
eventsSrc := string(eventsSource)
if !strings.Contains(eventsSrc, "func (b *AgentEventBroadcaster) PublishActionCompletedRecord(record unifiedresources.ActionAuditRecord)") {
t.Error("agent_events.go must keep a shared action-audit-record projector for every action.completed producer")
}
if !strings.Contains(eventsSrc, "ActionID: record.ID") {
t.Error("action.completed payload must carry the canonical action id so agents can correlate the event with /api/actions/{id}")
}
if !strings.Contains(eventsSrc, "ResourceID: record.Request.ResourceID") {
t.Error("action.completed payload must carry the canonical resource id so agents can match it against the rest of Pulse")
}
}
// TestContract_ExternalAgentActivityUsesRouteSpecificMarkers pins the
// anonymous usage signal behind Pulse Intelligence external-agent telemetry:
// configured/readiness may come from token capability posture, but recent-use
// must come from authenticated calls into a manifest-backed agent/MCP
// capability route. Generic API-token LastUsedAt is too broad because the same
// token can hit unrelated routes; requiring every manifest scope is too narrow
// because read-only MCP clients are supported.
func TestContract_ExternalAgentActivityUsesRouteSpecificMarkers(t *testing.T) {
activitySource, err := os.ReadFile("agent_activity_telemetry.go")
if err != nil {
t.Fatalf("read agent_activity_telemetry.go: %v", err)
}
activitySrc := string(activitySource)
for _, required := range []string{
"getAPITokenRecordFromRequest(req)",
"externalAgentCapabilityActivity(capabilityName)",
"agentcapabilities.FindCapability(agentcapabilities.CanonicalManifest().Capabilities, capabilityName)",
"apiTokenCoversExternalAgentSurface(token, time.Now().UTC(), requiredScopes...)",
"agentcapabilities.CanonicalManifest().RequiredScopes",
"func externalAgentActivityForCapability(capabilityName string) (string, bool)",
"persistence.RecordExternalAgentActivity(config.ExternalAgentActivityRecord{",
} {
if !strings.Contains(activitySrc, required) {
t.Errorf("external-agent activity marker must stay token-scoped and manifest-capable; missing %s", required)
}
}
readCapability, ok := agentcapabilities.FindCapability(agentcapabilities.CanonicalManifest().Capabilities, agentcapabilities.FleetContextCapabilityName)
if !ok {
t.Fatalf("manifest missing %s", agentcapabilities.FleetContextCapabilityName)
}
if _, requiredScope, ok := externalAgentCapabilityActivity(agentcapabilities.FleetContextCapabilityName); !ok || requiredScope != readCapability.Scope {
t.Fatalf("external-agent activity must use the called capability scope, got scope=%q ok=%v want %q", requiredScope, ok, readCapability.Scope)
}
for _, capability := range agentcapabilities.ManifestSurfaceToolCapabilities(agentcapabilities.CanonicalManifest(), agentcapabilities.SurfaceIDPulseMCP) {
if _, ok := externalAgentActivityForCapability(capability.Name); !ok {
t.Errorf("MCP-published capability %q must have an external-agent activity class", capability.Name)
}
}
routeFiles := []string{
"router_routes_monitoring.go",
"router_routes_registration.go",
"router_routes_ai_relay.go",
"router.go",
}
var routesBuilder strings.Builder
for _, file := range routeFiles {
routesSource, err := os.ReadFile(file)
if err != nil {
t.Fatalf("read %s: %v", file, err)
}
routesBuilder.Write(routesSource)
}
routesSrc := routesBuilder.String()
for _, required := range []string{
"agentcapabilities.ResourceContextCapabilityName",
"agentcapabilities.FleetContextCapabilityName",
"agentcapabilities.OperationsLoopStatusCapabilityName",
"agentcapabilities.EventSubscriptionCapabilityName",
"agentcapabilities.ListNodesCapabilityName",
"agentcapabilities.AddNodeCapabilityName",
"agentcapabilities.UpdateNodeCapabilityName",
"agentcapabilities.RemoveNodeCapabilityName",
"agentcapabilities.TestNodeCredentialsCapabilityName",
"agentcapabilities.TestNodeConnectionCapabilityName",
"agentcapabilities.RefreshNodeClusterMembershipCapabilityName",
"agentcapabilities.DiscoverLANCapabilityName",
"agentcapabilities.GetOperatorStateCapabilityName",
"agentcapabilities.SetOperatorStateCapabilityName",
"agentcapabilities.ClearOperatorStateCapabilityName",
"agentcapabilities.ListFindingsCapabilityName",
"agentcapabilities.AcknowledgeFindingCapabilityName",
"agentcapabilities.SnoozeFindingCapabilityName",
"agentcapabilities.DismissFindingCapabilityName",
"agentcapabilities.ResolveFindingCapabilityName",
"agentcapabilities.PlanActionCapabilityName",
"agentcapabilities.DecideActionCapabilityName",
"agentcapabilities.ExecuteActionCapabilityName",
} {
if !strings.Contains(routesSrc, required) {
t.Errorf("manifest-backed external-agent routes must record capability-specific activity; missing %s", required)
}
}
telemetrySource, err := os.ReadFile(filepath.Join("..", "..", "pkg", "server", "telemetry_pulse_intelligence.go"))
if err != nil {
t.Fatalf("read telemetry_pulse_intelligence.go: %v", err)
}
telemetrySrc := string(telemetrySource)
if !strings.Contains(telemetrySrc, "persistence.LoadExternalAgentActivityHistory()") {
t.Error("Pulse Intelligence external-agent recent-use telemetry must read route activity history")
}
for _, required := range []string{
"pulseIntelligenceExternalAgentSurfaceScopes(agentcapabilities.CanonicalManifest())",
"agentcapabilities.ManifestSurfaceToolCapabilities(manifest, agentcapabilities.SurfaceIDPulseMCP)",
"agentcapabilities.RequiredCapabilityScopes(",
"func pulseIntelligenceExternalAgentToken(token config.APITokenRecord, surfaceScopes []string, now time.Time) bool",
"if token.HasScope(scope) {\n\t\t\treturn true",
} {
if !strings.Contains(telemetrySrc, required) {
t.Errorf("Pulse Intelligence external-agent readiness telemetry must use MCP-published capability scopes; missing %s", required)
}
}
if strings.Contains(telemetrySrc, "LastUsedAt != nil && !token.LastUsedAt.UTC().Before(since)") {
t.Error("Pulse Intelligence external-agent recent-use telemetry must not infer usage from generic token LastUsedAt")
}
if strings.Contains(telemetrySrc, "requiredScopes := agentcapabilities.CanonicalManifest().RequiredScopes") {
t.Error("Pulse Intelligence external-agent readiness telemetry must not require every manifest scope")
}
}
// TestContract_UpdateFunnelTelemetryStaysContentFree pins the server-owned
// update funnel telemetry path. Update history may feed outbound usage adoption
// analysis, but only as aggregate 30-day counts and a coarse failure category,
// and the operator preview type must expose the same JSON fields.
func TestContract_UpdateFunnelTelemetryStaysContentFree(t *testing.T) {
pingType := reflect.TypeOf(telemetry.Ping{})
for _, field := range []string{
`UpdateAttempts30d:update_attempts_30d`,
`UpdateSuccesses30d:update_successes_30d`,
`UpdateFailures30d:update_failures_30d`,
`UpdateLastFailureCategory:update_last_failure_category,omitempty`,
} {
name, tag, _ := strings.Cut(field, ":")
sf, ok := pingType.FieldByName(name)
if !ok {
t.Fatalf("telemetry.Ping missing %s", name)
}
if got := sf.Tag.Get("json"); got != tag {
t.Fatalf("telemetry.Ping.%s json tag = %q, want %q", name, got, tag)
}
}
serverSource, err := os.ReadFile(filepath.Join("..", "..", "pkg", "server", "server.go"))
if err != nil {
t.Fatalf("read server.go: %v", err)
}
if !strings.Contains(string(serverSource), "router.ApplyUpdateTelemetrySnapshot(&snap, now)") {
t.Error("server telemetry snapshot must route update-history rollups through Router.ApplyUpdateTelemetrySnapshot before preview/send")
}
routerSource, err := os.ReadFile("telemetry_pulse_intelligence.go")
if err != nil {
t.Fatalf("read telemetry_pulse_intelligence.go: %v", err)
}
for _, fragment := range []string{
"func (r *Router) ApplyUpdateTelemetrySnapshot(s *telemetry.Snapshot, now time.Time)",
"counters to the outbound usage telemetry snapshot",
"telemetry.ApplyUpdateTelemetrySnapshot(s, r.updateHistory, now)",
} {
if !strings.Contains(string(routerSource), fragment) {
t.Errorf("router-owned update telemetry boundary missing %s", fragment)
}
}
if strings.Contains(string(routerSource), "anonymous telemetry") {
t.Error("router-owned update telemetry boundary must not describe outbound usage telemetry as anonymous")
}
settingsSource, err := os.ReadFile(filepath.Join("..", "..", "frontend-modern", "src", "api", "settings.ts"))
if err != nil {
t.Fatalf("read frontend settings API type: %v", err)
}
for _, fragment := range []string{
"update_attempts_30d: number;",
"update_successes_30d: number;",
"update_failures_30d: number;",
"update_last_failure_category?: string;",
} {
if !strings.Contains(string(settingsSource), fragment) {
t.Errorf("TelemetryPingPreview must mirror update funnel field %s", fragment)
}
}
}
// TestContract_ExecutorPostCompletionCallback pins the seam the SSE
// bridge depends on. The executor must expose SetOnActionCompleted
// and the action_audit hot path must dispatch the callback for every
// terminal-state record (success, runtime fail, plan-drift refusal,
// remediation-lock refusal). Removing or in-lining the callback
// breaks the bridge silently.
func TestContract_ExecutorPostCompletionCallback(t *testing.T) {
executor, err := os.ReadFile("../ai/tools/executor.go")
if err != nil {
t.Fatalf("read executor.go: %v", err)
}
executorSrc := string(executor)
if !strings.Contains(executorSrc, "func (e *PulseToolExecutor) SetOnActionCompleted(cb func(unifiedresources.ActionAuditRecord))") {
t.Error("executor must expose SetOnActionCompleted so the API layer can bridge action completion into the agent SSE stream")
}
audit, err := os.ReadFile("../ai/tools/action_audit.go")
if err != nil {
t.Fatalf("read action_audit.go: %v", err)
}
auditSrc := string(audit)
if !strings.Contains(auditSrc, "func (e *PulseToolExecutor) publishActionCompleted(record unifiedresources.ActionAuditRecord)") {
t.Error("action_audit.go must define publishActionCompleted helper so terminal-state writers route through one bridge point")
}
if !strings.Contains(auditSrc, "go cb(record)") {
t.Error("post-completion callback must run on its own goroutine to keep the dispatch hot path off any consumer's slowness")
}
// Pin that all four terminal sites call publishActionCompleted.
terminalSites := []string{
`e.recordActionLifecycle(record.ID, unifiedresources.ActionStateFailed, requestedBy, "plan drift refused")
e.publishActionCompleted(record)`,
`e.recordActionLifecycle(record.ID, unifiedresources.ActionStateFailed, requestedBy, "resource remediation lock refused")
e.publishActionCompleted(record)`,
`e.recordActionLifecycle(record.ID, record.State, actor, message)
e.publishActionCompleted(record)`,
}
for _, site := range terminalSites {
if !strings.Contains(auditSrc, site) {
t.Errorf("action_audit.go must call publishActionCompleted at every terminal-state site; missing block:\n%s", site)
}
}
}
// TestContract_AgentEventActionCompletedKindIsStable pins the
// wire-stable event kind string. Renaming AgentEventActionCompleted
// breaks every agent that branches on the SSE event-type field; the
// constant is part of the contract, not an implementation detail.
func TestContract_AgentEventActionCompletedKindIsStable(t *testing.T) {
if AgentEventActionCompleted != AgentEventKind(agentcapabilities.EventKindActionCompleted) {
t.Errorf("AgentEventActionCompleted = %q, want shared event kind %q",
AgentEventActionCompleted, agentcapabilities.EventKindActionCompleted)
}
if string(AgentEventActionCompleted) != "action.completed" {
t.Errorf("AgentEventActionCompleted wire value = %q, want action.completed", AgentEventActionCompleted)
}
}
// TestContract_SubscribeEventsCapabilityListsActionCompleted pins
// the discovery contract: the capabilities manifest must mention
// action.completed under subscribe_events so an agent reading the
// manifest learns the kind exists without out-of-band documentation.
// Drift here means external agents miss the event entirely because
// they didn't know to listen for it.
func TestContract_SubscribeEventsCapabilityListsActionCompleted(t *testing.T) {
capability, ok := agentcapabilities.FindCapability(agentcapabilities.CanonicalManifest().Capabilities, agentcapabilities.EventSubscriptionCapabilityName)
if !ok {
t.Fatal("canonical manifest must include subscribe_events")
}
if !strings.Contains(capability.Description, string(agentcapabilities.EventKindActionCompleted)+" when an action audit reaches a terminal state") {
t.Error("subscribe_events description must mention action.completed so agents discover the kind through the manifest")
}
}
// TestContract_AgentCapabilitiesManifestVersionIsPinned pins the
// manifest's version contract: bumping it is reserved for breaking
// shape changes, additive capabilities ship under the same version.
// Pinning the version string here means a refactor that accidentally
// bumps it shows up as a test failure rather than silently breaking
// every external agent that validates compatibility on startup.
func TestContract_AgentCapabilitiesManifestVersionIsPinned(t *testing.T) {
src := readAgentCapabilitiesManifestSource(t)
if !strings.Contains(src, `Version: "v1"`) {
t.Error("agent capabilities manifest must pin Version to v1; bumping is a breaking-change decision and should not happen silently")
}
for _, required := range []string{
`SurfaceContract: SurfaceContract{`,
`ID: "pulse_intelligence_core"`,
`Label: "Pulse Intelligence Core"`,
`ID: "pulse_patrol"`,
`ID: SurfaceIDPulseAssistant`,
`ID: SurfaceIDPulseMCP`,
`InteractiveQuestions: true`,
`CapabilityMetadata: true`,
`SurfaceToolContracts: []SurfaceToolContract{`,
`ToolNames: canonicalPulseMCPSurfaceToolNames(),`,
`publishedSurfaceToolContracts := CloneSurfaceToolContracts(canonicalManifest.SurfaceToolContracts)`,
`SurfaceToolContracts: CloneSurfaceToolContracts(surfaceToolContracts)`,
`WorkflowPrompts:`,
`ClonePulseWorkflowPrompts(ProjectPulseWorkflowPrompts(capabilities))`,
} {
if !strings.Contains(src, required) {
t.Errorf("agent capabilities manifest must pin the Pulse Intelligence surface contract; missing %s", required)
}
}
if strings.Contains(src, `ID: "pulse_patrol"`) {
t.Error("Pulse Patrol must stay the primary built-in operator component, not be flattened into the compatibility access-path list")
}
// Capability surface anchors — these names are agent-stable
// identifiers and renaming any of them breaks every external
// integration. Pin them here so renames must be deliberate.
required := []string{
agentcapabilities.ResourceContextCapabilityName,
agentcapabilities.GetOperatorStateCapabilityName,
agentcapabilities.SetOperatorStateCapabilityName,
agentcapabilities.ClearOperatorStateCapabilityName,
agentcapabilities.ListFindingsCapabilityName,
}
manifest := agentcapabilities.CanonicalManifest()
for _, name := range required {
if _, ok := agentcapabilities.FindCapability(manifest.Capabilities, name); !ok {
t.Errorf("agent capabilities manifest must declare canonical capability %q", name)
}
}
}
func TestContract_PulseIntelligenceSurfaceToolProjectionKeepsAssistantAndMCPDistinct(t *testing.T) {
surfaceSource, err := os.ReadFile(filepath.Join("..", "agentcapabilities", "surface_contract.go"))
if err != nil {
t.Fatalf("read internal/agentcapabilities/surface_contract.go: %v", err)
}
surfaceContract := string(surfaceSource)
for _, required := range []string{
`type SurfaceToolContract struct`,
`SurfaceToolSourceAssistantRegistry = "assistant_registry"`,
`SurfaceToolSourceCapabilityManifest = "capability_manifest"`,
`func ProjectPulseAssistantSurfaceToolContract(`,
`func ProjectManifestSurfaceToolContract(`,
`func ProjectManifestSurfaceToolContracts(`,
`func ResolveManifestSurfaceToolContract(`,
`func ProjectPulseIntelligenceSurfaceToolContracts(`,
`type ResolvedSurfaceAffordanceContract struct`,
`func ResolveSurfaceAffordanceContract(`,
`ResolveSurfaceAffordanceContract(contract, SurfaceIDPulseAssistant, "")`,
`ResolveSurfaceAffordanceContract(manifest.SurfaceContract, surface.ID, surface.Label)`,
`func ManifestSurfaceAffordances(`,
`func IntersectSurfaceAffordances(`,
`IntersectSurfaceAffordances(resolvedSurface.Affordances, contract.Affordances)`,
`ProviderToolNames(providerTools)`,
`AssistantNativeProviderToolNames()`,
`if !surface.Affordances.Tools`,
`if !surface.Affordances.InteractiveQuestions`,
`ToolNames: offeredNames`,
`!surface.ExternalAdapter`,
`FindManifestSurfaceToolContract(manifest, surface.ID)`,
`requestResponseCapabilityNamesForNames(manifest.Capabilities, contract.ToolNames)`,
`ProjectManifestSurfaceToolContract(manifest, surface.ID)`,
} {
if !strings.Contains(surfaceContract, required) {
t.Errorf("agentcapabilities must own shared Assistant/MCP surface tool projection; missing %s", required)
}
}
for _, forbidden := range []string{
`func ProjectPulseMCPSurfaceToolContract(`,
`projectManifestSurfaceToolContractFromCapabilities`,
`requestResponseCapabilityNames(manifest.Capabilities)`,
`ProjectManifestSurfaceToolContract(manifest, SurfaceIDPulseMCP)`,
`ProjectTools(manifest.Capabilities)`,
} {
if strings.Contains(surfaceContract, forbidden) {
t.Errorf("agentcapabilities surface resolver must not keep MCP-specific or raw-capability fallback projection; found %s", forbidden)
}
}
projectionSource, err := os.ReadFile(filepath.Join("..", "agentcapabilities", "projection.go"))
if err != nil {
t.Fatalf("read internal/agentcapabilities/projection.go: %v", err)
}
projectionContract := string(projectionSource)
for _, required := range []string{
`ResolveManifestSurfaceToolContract(manifest, surfaceID)`,
`ManifestSurfaceAffordances(manifest, contract.SurfaceID)`,
`if !affordances.Tools`,
} {
if !strings.Contains(projectionContract, required) {
t.Errorf("agentcapabilities projection must resolve external-surface tools through the normalized surface contract; missing %s", required)
}
}
assistantProjectionSource, err := os.ReadFile(filepath.Join("..", "ai", "tools", "provider_projection.go"))
if err != nil {
t.Fatalf("read internal/ai/tools/provider_projection.go: %v", err)
}
assistantProjection := string(assistantProjectionSource)
for _, required := range []string{
`func (e *PulseToolExecutor) AssistantSurfaceToolContract(opts agentcapabilities.AssistantProviderToolOptions) agentcapabilities.SurfaceToolContract`,
`agentcapabilities.ProjectPulseAssistantSurfaceToolContract(`,
`agentcapabilities.CanonicalManifest().SurfaceContract`,
`e.AssistantProviderTools(opts)`,
} {
if !strings.Contains(assistantProjection, required) {
t.Errorf("Assistant executor must expose native surface projection through shared core contract; missing %s", required)
}
}
chatServiceSource, err := os.ReadFile(filepath.Join("..", "ai", "chat", "service.go"))
if err != nil {
t.Fatalf("read internal/ai/chat/service.go: %v", err)
}
chatService := string(chatServiceSource)
for _, required := range []string{
`func (s *Service) AssistantSurfaceToolContract(_ context.Context) agentcapabilities.SurfaceToolContract`,
`executor.AssistantSurfaceToolContract(agentcapabilities.AssistantProviderToolOptions{`,
`IncludeQuestionTool: !s.isAutonomousModeEnabled(),`,
} {
if !strings.Contains(chatService, required) {
t.Errorf("chat service must expose live native Assistant surface tools through the shared core contract; missing %s", required)
}
}
aiHandlerSource, err := os.ReadFile("ai_handler.go")
if err != nil {
t.Fatalf("read internal/api/ai_handler.go: %v", err)
}
aiHandler := string(aiHandlerSource)
for _, required := range []string{
`AssistantSurfaceToolContract(ctx context.Context) agentcapabilities.SurfaceToolContract`,
`func (h *AIHandler) HandleAssistantSurfaceTools(w http.ResponseWriter, r *http.Request)`,
`svc.AssistantSurfaceToolContract(ctx)`,
`Pulse Assistant is not running`,
} {
if !strings.Contains(aiHandler, required) {
t.Errorf("AI handler must expose authenticated native Assistant surface tool contract from runtime service; missing %s", required)
}
}
aiRelaySource, err := os.ReadFile("router_routes_ai_relay.go")
if err != nil {
t.Fatalf("read internal/api/router_routes_ai_relay.go: %v", err)
}
aiRelay := string(aiRelaySource)
for _, required := range []string{
`"/api/ai/assistant/surface-tools"`,
`RequireScope(config.ScopeAIChat, r.aiHandler.HandleAssistantSurfaceTools)`,
} {
if !strings.Contains(aiRelay, required) {
t.Errorf("Assistant surface tools endpoint must stay authenticated behind ai:chat route scope; missing %s", required)
}
}
manifest := agentcapabilities.CanonicalManifest()
contracts := agentcapabilities.ProjectPulseIntelligenceSurfaceToolContracts(manifest, []agentcapabilities.ProviderTool{
{Name: agentcapabilities.PulseQueryToolName},
{Name: agentcapabilities.PulseReadToolName},
agentcapabilities.NewPulseQuestionProviderTool(),
})
if len(contracts) != 2 {
t.Fatalf("Pulse Intelligence surface tool contracts = %+v, want Assistant and MCP", contracts)
}
bySurface := map[string]agentcapabilities.SurfaceToolContract{}
for _, contract := range contracts {
bySurface[contract.SurfaceID] = contract
}
assistant := bySurface[agentcapabilities.SurfaceIDPulseAssistant]
if assistant.ToolSource != agentcapabilities.SurfaceToolSourceAssistantRegistry {
t.Fatalf("Assistant tool source = %q", assistant.ToolSource)
}
if !reflect.DeepEqual(assistant.ToolNames, []string{
agentcapabilities.PulseQueryToolName,
agentcapabilities.PulseReadToolName,
agentcapabilities.PulseQuestionToolName,
}) {
t.Fatalf("Assistant surface tool names = %#v", assistant.ToolNames)
}
if !reflect.DeepEqual(assistant.NativeToolNames, []string{agentcapabilities.PulseQuestionToolName}) {
t.Fatalf("Assistant native tool names = %#v", assistant.NativeToolNames)
}
if len(assistant.CapabilityNames) != 0 {
t.Fatalf("Assistant surface must not duplicate MCP manifest capabilities: %#v", assistant.CapabilityNames)
}
mcp := bySurface[agentcapabilities.SurfaceIDPulseMCP]
if mcp.ToolSource != agentcapabilities.SurfaceToolSourceCapabilityManifest {
t.Fatalf("MCP tool source = %q", mcp.ToolSource)
}
if len(mcp.CapabilityNames) == 0 || len(mcp.ToolNames) != len(mcp.CapabilityNames) {
t.Fatalf("MCP tools must be manifest request/response capabilities, got tools=%#v capabilities=%#v", mcp.ToolNames, mcp.CapabilityNames)
}
if !reflect.DeepEqual(mcp.ToolNames, manifest.SurfaceToolContracts[0].ToolNames) {
t.Fatalf("MCP tools must come from manifest-published surface contract, got tools=%#v published=%#v", mcp.ToolNames, manifest.SurfaceToolContracts[0].ToolNames)
}
for _, forbidden := range []string{
agentcapabilities.PulseQuestionToolName,
agentcapabilities.EventSubscriptionCapabilityName,
} {
if stringSliceContains(mcp.ToolNames, forbidden) {
t.Fatalf("MCP tools must not include %s; names=%#v", forbidden, mcp.ToolNames)
}
}
if len(mcp.RegistryToolNames) != 0 || len(mcp.NativeToolNames) != 0 {
t.Fatalf("MCP surface must not expose Assistant registry/native tool buckets: %+v", mcp)
}
disabledManifest := manifest
disabledManifest.SurfaceContract = agentcapabilities.CloneSurfaceContract(manifest.SurfaceContract)
for i := range disabledManifest.SurfaceContract.OperatorSurfaces {
if disabledManifest.SurfaceContract.OperatorSurfaces[i].ID == agentcapabilities.SurfaceIDPulseMCP {
disabledManifest.SurfaceContract.OperatorSurfaces[i].Affordances = agentcapabilities.SurfaceAffordanceContract{
Resources: true,
Prompts: true,
CapabilityMetadata: true,
}
}
}
disabledContracts := agentcapabilities.ProjectPulseIntelligenceSurfaceToolContracts(disabledManifest, []agentcapabilities.ProviderTool{
{Name: agentcapabilities.PulseQueryToolName},
agentcapabilities.NewPulseQuestionProviderTool(),
})
disabledBySurface := map[string]agentcapabilities.SurfaceToolContract{}
for _, contract := range disabledContracts {
disabledBySurface[contract.SurfaceID] = contract
}
disabledMCP := disabledBySurface[agentcapabilities.SurfaceIDPulseMCP]
if disabledMCP.Affordances.Tools || len(disabledMCP.ToolNames) != 0 || len(disabledMCP.CapabilityNames) != 0 {
t.Fatalf("MCP disabled tools affordance must clear static surface tools, got %+v", disabledMCP)
}
}
func TestContract_AgentCapabilitiesManifestScopesUseAuthConstants(t *testing.T) {
src := readAgentCapabilitiesManifestSource(t)
for _, required := range []string{
`agentCapabilityScopeMonitoringRead = auth.ScopeMonitoringRead`,
`agentCapabilityScopeMonitoringWrite = auth.ScopeMonitoringWrite`,
`agentCapabilityScopeSettingsRead = auth.ScopeSettingsRead`,
`agentCapabilityScopeSettingsWrite = auth.ScopeSettingsWrite`,
`agentCapabilityScopeAIExecute = auth.ScopeAIExecute`,
} {
if !strings.Contains(src, required) {
t.Errorf("agent capabilities manifest must pin scope vocabulary to pkg/auth; missing %s", required)
}
}
if strings.Contains(src, `Scope: "`) || strings.Contains(src, `Scope: "`) {
t.Error("agent capabilities manifest must not declare scope values as local string literals")
}
known := map[string]bool{}
for _, scope := range authpkg.AllKnownScopes {
known[scope] = true
}
for _, cap := range agentcapabilities.CanonicalManifest().Capabilities {
if !known[cap.Scope] {
t.Errorf("capability %q scope = %q, want auth-owned scope vocabulary", cap.Name, cap.Scope)
}
}
}
func TestContract_AgentCapabilitiesFrontendTypesAreGeneratedFromManifest(t *testing.T) {
generatorSource, err := os.ReadFile(filepath.Join("..", "..", "scripts", "generate-types.go"))
if err != nil {
t.Fatalf("read scripts/generate-types.go: %v", err)
}
generator := string(generatorSource)
for _, required := range []string{
`"github.com/rcourtman/pulse-go-rewrite/internal/agentcapabilities"`,
`filepath.Join("frontend-modern", "src", "api", "generated", "agentCapabilities.ts")`,
`reflect.TypeOf(agentcapabilities.Capability{})`,
`reflect.TypeOf(agentcapabilities.CapabilityCategory{})`,
`reflect.TypeOf(agentcapabilities.Manifest{})`,
`reflect.TypeOf(agentcapabilities.MCPAdapterConfigFamily{})`,
`reflect.TypeOf(agentcapabilities.MCPAdapterContract{})`,
`reflect.TypeOf(agentcapabilities.OperatorSurfaceContract{})`,
`reflect.TypeOf(agentcapabilities.PulseWorkflowPrompt{})`,
`reflect.TypeOf(agentcapabilities.PulseWorkflowPromptArgument{})`,
`reflect.TypeOf(agentcapabilities.SurfaceAffordanceContract{})`,
`reflect.TypeOf(agentcapabilities.SurfaceContract{})`,
`reflect.TypeOf(agentcapabilities.SurfaceContractComponent{})`,
`reflect.TypeOf(agentcapabilities.SurfaceToolContract{})`,
`func agentCapabilitiesAliases() string`,
`return "AgentCapabilityActionMode"`,
`return "AgentCapabilityApprovalPolicy"`,
`return "Record<string, unknown>"`,
} {
if !strings.Contains(generator, required) {
t.Errorf("generate-types.go must derive frontend agent capabilities types from internal/agentcapabilities; missing %s", required)
}
}
generatedSource, err := os.ReadFile(filepath.Join("..", "..", "frontend-modern", "src", "api", "generated", "agentCapabilities.ts"))
if err != nil {
t.Fatalf("read generated agent capabilities frontend types: %v", err)
}
generated := string(generatedSource)
for _, required := range []string{
`// This file is generated from scripts/generate-types.go; DO NOT EDIT.`,
`// Source: internal/agentcapabilities manifest structs.`,
`export type AgentCapabilityActionMode = 'read' | 'mixed' | 'write';`,
`export type AgentCapabilityApprovalPolicy = 'scope_only' | 'action_plan';`,
`export interface Capability {`,
`title?: string;`,
`actionMode: AgentCapabilityActionMode;`,
`approvalPolicy: AgentCapabilityApprovalPolicy;`,
`inputSchema?: Record<string, unknown>;`,
`outputSchema?: Record<string, unknown>;`,
`export interface CapabilityCategory {`,
`export interface MCPAdapterConfigFamily {`,
`clientLabels?: string[];`,
`export interface MCPAdapterContract {`,
`serverName: string;`,
`command: string;`,
`baseUrlFlag: string;`,
`defaultBaseUrl: string;`,
`tokenEnv: string;`,
`configFamilies: MCPAdapterConfigFamily[];`,
`export interface Manifest {`,
`surfaceContract: SurfaceContract;`,
`surfaceToolContracts: SurfaceToolContract[];`,
`mcpAdapter: MCPAdapterContract;`,
`requiredScopes: string[];`,
`categories: CapabilityCategory[];`,
`workflowPrompts: PulseWorkflowPrompt[];`,
`capabilities: Capability[];`,
`export interface PulseWorkflowPrompt {`,
`label?: string;`,
`presentationKind?: string;`,
`arguments?: PulseWorkflowPromptArgument[];`,
`export interface PulseWorkflowPromptArgument {`,
`export interface SurfaceContract {`,
`core: SurfaceContractComponent;`,
`proactiveEngine: SurfaceContractComponent;`,
`operatorSurfaces: OperatorSurfaceContract[];`,
`export interface OperatorSurfaceContract {`,
`native: boolean;`,
`externalAdapter: boolean;`,
`affordances?: SurfaceAffordanceContract;`,
`export interface SurfaceAffordanceContract {`,
`interactiveQuestions?: boolean;`,
`export interface SurfaceToolContract {`,
`surfaceId: string;`,
`toolSource: string;`,
`toolNames: string[];`,
`registryToolNames?: string[];`,
`capabilityNames?: string[];`,
`nativeToolNames?: string[];`,
} {
if !strings.Contains(generated, required) {
t.Errorf("generated agent capabilities frontend types drifted from manifest contract; missing %s", required)
}
}
clientSource, err := os.ReadFile(filepath.Join("..", "..", "frontend-modern", "src", "api", "agentCapabilities.ts"))
if err != nil {
t.Fatalf("read frontend agent capabilities client: %v", err)
}
client := string(clientSource)
for _, required := range []string{
`from './generated/agentCapabilities'`,
`export type AgentCapability = Capability;`,
`export type AgentCapabilityCategory = CapabilityCategory;`,
`export type AgentCapabilitiesManifest = Manifest;`,
`export type AgentMCPAdapterConfigFamily = MCPAdapterConfigFamily;`,
`export type AgentMCPAdapterContract = MCPAdapterContract;`,
`export type AgentOperatorSurfaceContract = OperatorSurfaceContract;`,
`export type AgentWorkflowPrompt = PulseWorkflowPrompt;`,
`export type AgentWorkflowPromptArgument = PulseWorkflowPromptArgument;`,
`export type AgentSurfaceAffordanceContract = SurfaceAffordanceContract;`,
`export type AgentSurfaceContractComponent = SurfaceContractComponent;`,
`export type AgentSurfaceToolContract = SurfaceToolContract;`,
`export interface AgentSurfaceToolPosturePresentation {`,
`export interface AgentSurfaceContractEntry {`,
`normalizeAgentSurfaceToolContract(`,
`findAgentSurfaceToolContract(`,
`getAgentManifestSurfaceToolContract(`,
`getAgentManifestSurfaceToolContracts(`,
`getAgentSurfaceToolPosturePresentation(`,
`normalizeAgentMCPAdapter(`,
`getAgentMCPClientExamples(`,
`formatAgentOpenCodeMCPConfig(`,
`formatAgentMCPServersConfig(`,
`getAgentSurfaceContractEntries(`,
`getAgentWorkflowPrompts(`,
`normalizeSurfaceAffordances(`,
`surfaceAffordanceLabels(`,
`manifest?.workflowPrompts`,
`manifest.surfaceContract?.core`,
`manifest.surfaceContract?.proactiveEngine`,
`manifest.surfaceContract?.operatorSurfaces`,
`manifest?.surfaceToolContracts`,
`manifest?.mcpAdapter`,
} {
if !strings.Contains(client, required) {
t.Errorf("frontend agent capabilities client must consume generated manifest types; missing %s", required)
}
}
for _, forbidden := range []string{
`export interface AgentCapability {`,
`export interface AgentCapabilityCategory {`,
`export interface AgentCapabilitiesManifest {`,
`export interface AgentMCPAdapterContract {`,
`AGENT_CAPABILITY_NAME_SUBSCRIBE_EVENTS`,
`getRequestResponseCapabilityNames(`,
`isRequestResponseAgentCapability(`,
`getAgentMCPSurfaceToolContract(`,
`subscribe_events`,
} {
if strings.Contains(client, forbidden) {
t.Errorf("frontend agent capabilities client must not redeclare manifest types locally; found %s", forbidden)
}
}
panelSource, err := os.ReadFile(filepath.Join("..", "..", "frontend-modern", "src", "components", "Settings", "AgentIntegrationsPanel.tsx"))
if err != nil {
t.Fatalf("read AgentIntegrationsPanel.tsx: %v", err)
}
panel := string(panelSource)
for _, required := range []string{
`getAgentSurfaceContractEntries`,
`normalizeAgentMCPAdapter`,
`formatAgentOpenCodeMCPConfig`,
`formatAgentMCPServersConfig`,
`AGENT_SURFACE_ID_PULSE_MCP`,
`getAgentManifestSurfaceToolContract`,
`getAgentSurfaceToolPosturePresentation`,
`surfaceContractEntries`,
`mcpSurfaceToolPosture`,
`data-testid="agent-mcp-tool-posture"`,
`External agents expose`,
} {
if !strings.Contains(panel, required) {
t.Errorf("Agent integrations panel must project the manifest surface contract through the shared client; missing %s", required)
}
}
for _, forbidden := range []string{
`Pulse Intelligence Core`,
`Pulse Assistant`,
`Pulse Patrol`,
`Pulse MCP`,
} {
if strings.Contains(panel, forbidden) {
t.Errorf("Agent integrations panel must not duplicate manifest surface labels locally; found %s", forbidden)
}
}
for _, forbidden := range []string{
`const MCP_CLIENT_EXAMPLES`,
`function formatClaudeMcpConfig`,
`function formatOpenCodeMcpConfig`,
`command: 'pulse-mcp'`,
`PULSE_API_TOKEN: '<your-api-token>'`,
`getAgentMCPClientExamples`,
`getAgentMCPSurfaceToolContract`,
`subscribe_events`,
`capability.name`,
} {
if strings.Contains(panel, forbidden) {
t.Errorf("Agent integrations panel must consume manifest-backed MCP adapter helpers instead of local setup snippets; found %s", forbidden)
}
}
assistantChatSource, err := os.ReadFile(filepath.Join("..", "..", "frontend-modern", "src", "components", "AI", "Chat", "index.tsx"))
if err != nil {
t.Fatalf("read AI Chat index.tsx: %v", err)
}
assistantChat := string(assistantChatSource)
for _, required := range []string{
`getAgentSurfaceToolPosturePresentation`,
`type AgentSurfaceToolContract`,
`AIChatAPI.getAssistantSurfaceTools()`,
`data-testid="assistant-surface-tools-health"`,
} {
if !strings.Contains(assistantChat, required) {
t.Errorf("Assistant chat shell must consume the authenticated runtime surface-tool contract through the shared frontend client; missing %s", required)
}
}
for _, forbidden := range []string{
`'pulse_query'`,
`"pulse_query"`,
`'pulse_read'`,
`"pulse_read"`,
`'pulse_question'`,
`"pulse_question"`,
} {
if strings.Contains(assistantChat, forbidden) {
t.Errorf("Assistant chat shell must not hardcode native Assistant tool names for surface posture; found %s", forbidden)
}
}
}
func TestContract_AgentCapabilitiesPatrolFindingScopesMatchAPIAuthorization(t *testing.T) {
manifest := agentcapabilities.CanonicalManifest()
byName := map[string]agentcapabilities.Capability{}
for _, cap := range manifest.Capabilities {
byName[cap.Name] = cap
}
relayBacked := map[string]relayMobileRuntimeRouteID{
agentcapabilities.ListFindingsCapabilityName: relayMobileRoutePatrolFindingsList,
agentcapabilities.AcknowledgeFindingCapabilityName: relayMobileRoutePatrolAcknowledge,
agentcapabilities.SnoozeFindingCapabilityName: relayMobileRoutePatrolSnooze,
agentcapabilities.DismissFindingCapabilityName: relayMobileRoutePatrolDismiss,
}
for capabilityName, routeID := range relayBacked {
capability, ok := byName[capabilityName]
if !ok {
t.Fatalf("agent capabilities manifest missing %s", capabilityName)
}
route := relayMobileRuntimeRouteSpecFor(routeID)
if capability.Method != route.method || capability.Path != route.path || capability.Scope != route.requiredScope {
t.Fatalf("%s manifest route/scope = %s %s => %s, want API authorization route %s %s => %s",
capabilityName, capability.Method, capability.Path, capability.Scope, route.method, route.path, route.requiredScope)
}
}
resolve, ok := byName[agentcapabilities.ResolveFindingCapabilityName]
if !ok {
t.Fatalf("agent capabilities manifest missing %s", agentcapabilities.ResolveFindingCapabilityName)
}
if resolve.Method != http.MethodPost || resolve.Path != "/api/ai/patrol/resolve" || resolve.Scope != config.ScopeAIExecute {
t.Fatalf("resolve_finding manifest route/scope = %s %s => %s, want API authorization route POST /api/ai/patrol/resolve => %s",
resolve.Method, resolve.Path, resolve.Scope, config.ScopeAIExecute)
}
var resolveSchema struct {
Properties map[string]json.RawMessage `json:"properties"`
Required []string `json:"required"`
}
if err := json.Unmarshal(resolve.InputSchema, &resolveSchema); err != nil {
t.Fatalf("decode resolve_finding input schema: %v", err)
}
if _, ok := resolveSchema.Properties[agentcapabilities.ResolutionNoteArgumentName]; !ok {
t.Fatalf("resolve_finding input schema missing optional %s property", agentcapabilities.ResolutionNoteArgumentName)
}
for _, required := range resolveSchema.Required {
if required == agentcapabilities.ResolutionNoteArgumentName {
t.Fatalf("resolve_finding input schema made optional %s required", agentcapabilities.ResolutionNoteArgumentName)
}
}
dismiss := byName[agentcapabilities.DismissFindingCapabilityName]
var dismissSchema struct {
Properties map[string]json.RawMessage `json:"properties"`
Required []string `json:"required"`
}
if err := json.Unmarshal(dismiss.InputSchema, &dismissSchema); err != nil {
t.Fatalf("decode dismiss_finding input schema: %v", err)
}
if _, ok := dismissSchema.Properties[agentcapabilities.NoteArgumentName]; !ok {
t.Fatalf("dismiss_finding input schema missing optional %s property", agentcapabilities.NoteArgumentName)
}
for _, required := range dismissSchema.Required {
if required == agentcapabilities.NoteArgumentName {
t.Fatalf("dismiss_finding input schema made optional %s required", agentcapabilities.NoteArgumentName)
}
}
}
func TestContract_AgentCapabilitiesManifestRoutesReachRouterWithAdvertisedScope(t *testing.T) {
rawToken := "agent-route-contract-token.12345678"
record := newTokenRecord(t, rawToken, []string{config.ScopeAgentReport}, nil)
cfg := newTestConfigWithTokens(t, record)
router := NewRouter(cfg, nil, nil, nil, nil, "1.0.0")
args := map[string]any{
"actionId": "act_contract",
"capabilityName": "restart",
"duration_hours": 1,
agentcapabilities.FindingIDArgumentName: "finding-contract",
"host": "https://pve-contract.example.com:8006",
"intentionallyOffline": false,
"name": "contract-node",
"neverAutoRemediate": false,
"nodeId": "pve-contract",
"outcome": "approved",
"params": map[string]any{},
"password": "not-a-real-secret",
agentcapabilities.ReasonArgumentName: "contract proof",
"requestedBy": "agent:contract",
"requestId": "req-contract",
"resourceId": "vm101",
"subnet": "auto",
"type": "pve",
"use_cache": true,
"user": "root@pam",
"verifySSL": false,
"monitorVMs": true,
"monitorContainers": true,
"monitorStorage": true,
"monitorBackups": true,
"monitorPhysicalDisks": true,
"temperatureMonitoringEnabled": true,
}
for _, capability := range agentcapabilities.CanonicalManifest().Capabilities {
capability := capability
t.Run(capability.Name, func(t *testing.T) {
projected, err := agentcapabilities.ProjectCapabilityCall(capability, args)
if err != nil {
t.Fatalf("project manifest route %s %s: %v", capability.Method, capability.Path, err)
}
body := bytes.NewReader(nil)
if projected.HasBody {
body = bytes.NewReader(projected.Body)
}
req := httptest.NewRequest(capability.Method, projected.Path, body)
req.Header.Set(agentcapabilities.AgentAPITokenHeader, rawToken)
if projected.HasBody {
req.Header.Set("Content-Type", "application/json")
}
rec := httptest.NewRecorder()
router.Handler().ServeHTTP(rec, req)
if rec.Code != http.StatusForbidden {
t.Fatalf("%s projected to %s %s, got HTTP %d body=%s, want missing-scope 403 for %s",
capability.Name, capability.Method, projected.Path, rec.Code, rec.Body.String(), capability.Scope)
}
if !strings.Contains(rec.Body.String(), capability.Scope) {
t.Fatalf("%s projected to %s %s, missing-scope body %q does not mention advertised scope %q",
capability.Name, capability.Method, projected.Path, rec.Body.String(), capability.Scope)
}
})
}
}
func TestContract_AgentCapabilitiesRequiredScopeSummaryUsesManifestScopes(t *testing.T) {
scopesSource, err := os.ReadFile(filepath.Join("..", "agentcapabilities", "scopes.go"))
if err != nil {
t.Fatalf("read internal/agentcapabilities/scopes.go: %v", err)
}
src := string(scopesSource)
for _, required := range []string{
`func NormalizeRequiredScopes(scopes []string) []string`,
`func RequiredCapabilityScopes(capabilities []Capability) []string`,
`for _, scope := range auth.AllKnownScopes`,
`func RequiredScopeList(scopes []string) string`,
`func RequiredCapabilityScopeList(capabilities []Capability) string`,
`func ManifestRequiredScopeList(manifest *Manifest) string`,
} {
if !strings.Contains(src, required) {
t.Errorf("agent capabilities scope summary must stay in the shared manifest contract; missing %s", required)
}
}
got := agentcapabilities.RequiredCapabilityScopes(agentcapabilities.CanonicalManifest().Capabilities)
want := []string{
authpkg.ScopeMonitoringRead,
authpkg.ScopeMonitoringWrite,
authpkg.ScopeSettingsRead,
authpkg.ScopeSettingsWrite,
authpkg.ScopeAIExecute,
}
if !reflect.DeepEqual(got, want) {
t.Fatalf("canonical agent capability scopes = %v, want %v", got, want)
}
manifest := agentcapabilities.CanonicalManifest()
if !reflect.DeepEqual(manifest.RequiredScopes, want) {
t.Fatalf("canonical agent capability manifest requiredScopes = %v, want %v", manifest.RequiredScopes, want)
}
mcpSource, err := os.ReadFile(filepath.Join("..", "..", "cmd", "pulse-mcp", "main.go"))
if err != nil {
t.Fatalf("read cmd/pulse-mcp/main.go: %v", err)
}
mcp := string(mcpSource)
if !strings.Contains(mcp, `agentcapabilities.ManifestRequiredScopeList(manifest)`) {
t.Error("pulse-mcp token guidance must consume the manifest-owned requiredScopes summary")
}
if strings.Contains(mcp, `RequiredCapabilityScopeList(capabilities)`) {
t.Error("pulse-mcp token guidance must not recompute current manifest scopes from capability rows")
}
if strings.Contains(mcp, `monitoring:read scope (and monitoring:write`) {
t.Error("pulse-mcp startup guidance must not carry a hand-maintained partial scope list")
}
}
func TestContract_PulseMCPReadmeManifestProjectionStaysGenerated(t *testing.T) {
readmeBytes, err := os.ReadFile(filepath.Join("..", "..", "cmd", "pulse-mcp", "README.md"))
if err != nil {
t.Fatalf("read cmd/pulse-mcp/README.md: %v", err)
}
readme := string(readmeBytes)
manifest := agentcapabilities.CanonicalManifest()
generatorSource, err := os.ReadFile(filepath.Join("..", "..", "scripts", "generate-pulse-intelligence-docs.go"))
if err != nil {
t.Fatalf("read scripts/generate-pulse-intelligence-docs.go: %v", err)
}
generator := string(generatorSource)
for _, required := range []string{
"regeneratePulseIntelligenceDocs",
"regeneratePulseMCPReadme",
"regeneratePublicPulseIntelligenceOverview",
"agentcapabilities.MCPSurfaceContractMarkdown(manifest.SurfaceContract)",
"agentcapabilities.PulseIntelligenceOverviewMarkdown(manifest.SurfaceContract)",
"agentcapabilities.MCPClientConfigMarkdown(manifest.MCPAdapter)",
"agentcapabilities.MCPToolCapabilityInventoryMarkdown(manifest)",
"agentcapabilities.MCPPromptInventoryMarkdown(manifest)",
"agentcapabilities.MCPErrorCodeInventoryMarkdown(manifest)",
"agentcapabilities.PulseIntelligenceOverviewStartMarker",
"agentcapabilities.MCPReadmeSurfaceContractStartMarker",
"agentcapabilities.MCPReadmeClientConfigStartMarker",
} {
if !strings.Contains(generator, required) {
t.Errorf("Pulse Intelligence docs generator must own manifest-backed docs projection; missing %q", required)
}
}
markdownSource, err := os.ReadFile(filepath.Join("..", "agentcapabilities", "markdown.go"))
if err != nil {
t.Fatalf("read internal/agentcapabilities/markdown.go: %v", err)
}
markdown := string(markdownSource)
for _, required := range []string{
"func MCPPromptInventoryMarkdown(manifest Manifest) string",
"MCPManifestSurfacePromptProjectionSupported(manifest, SurfaceIDPulseMCP)",
"ProjectMCPWorkflowPrompts(ManifestPulseWorkflowPrompts(manifest))",
} {
if !strings.Contains(markdown, required) {
t.Errorf("MCP prompt inventory docs must use the shared surface prompt projection; missing %q", required)
}
}
if strings.Contains(markdown, "prompts := ManifestPulseWorkflowPrompts(manifest)") {
t.Fatal("MCP prompt inventory docs must not project raw workflow prompts without the MCP surface affordance gate")
}
surfaceBlock, err := markedContractBlock(readme, agentcapabilities.MCPReadmeSurfaceContractStartMarker, agentcapabilities.MCPReadmeSurfaceContractEndMarker)
if err != nil {
t.Fatal(err)
}
wantSurfaceBlock := agentcapabilities.MCPSurfaceContractMarkdown(manifest.SurfaceContract)
if surfaceBlock != wantSurfaceBlock {
t.Fatalf("pulse-mcp README surface-contract block drifted from canonical manifest; run `go run ./scripts/generate-pulse-intelligence-docs.go`\n%s\nwant:\n%s", surfaceBlock, wantSurfaceBlock)
}
scopeBlock, err := markedContractBlock(readme, agentcapabilities.MCPReadmeScopeListStartMarker, agentcapabilities.MCPReadmeScopeListEndMarker)
if err != nil {
t.Fatal(err)
}
wantScopeBlock := agentcapabilities.ManifestRequiredScopeMarkdownList(manifest)
if scopeBlock != wantScopeBlock {
t.Fatalf("pulse-mcp README scope block drifted from canonical manifest:\n%s\nwant:\n%s", scopeBlock, wantScopeBlock)
}
clientConfigBlock, err := markedContractBlock(readme, agentcapabilities.MCPReadmeClientConfigStartMarker, agentcapabilities.MCPReadmeClientConfigEndMarker)
if err != nil {
t.Fatal(err)
}
wantClientConfigBlock := agentcapabilities.MCPClientConfigMarkdown(manifest.MCPAdapter)
if clientConfigBlock != wantClientConfigBlock {
t.Fatalf("pulse-mcp README client config block drifted from canonical manifest; run `go run ./scripts/generate-pulse-intelligence-docs.go`\n%s\nwant:\n%s", clientConfigBlock, wantClientConfigBlock)
}
toolBlock, err := markedContractBlock(readme, agentcapabilities.MCPReadmeToolInventoryStartMarker, agentcapabilities.MCPReadmeToolInventoryEndMarker)
if err != nil {
t.Fatal(err)
}
wantToolBlock := agentcapabilities.MCPToolCapabilityInventoryMarkdown(manifest)
if toolBlock != wantToolBlock {
t.Fatalf("pulse-mcp README tool block drifted from canonical manifest; run `go run ./scripts/generate-pulse-intelligence-docs.go`\n%s\nwant:\n%s", toolBlock, wantToolBlock)
}
promptBlock, err := markedContractBlock(readme, agentcapabilities.MCPReadmePromptInventoryStartMarker, agentcapabilities.MCPReadmePromptInventoryEndMarker)
if err != nil {
t.Fatal(err)
}
wantPromptBlock := agentcapabilities.MCPPromptInventoryMarkdown(manifest)
if promptBlock != wantPromptBlock {
t.Fatalf("pulse-mcp README prompt block drifted from canonical manifest; run `go run ./scripts/generate-pulse-intelligence-docs.go`\n%s\nwant:\n%s", promptBlock, wantPromptBlock)
}
errorBlock, err := markedContractBlock(readme, agentcapabilities.MCPReadmeErrorInventoryStartMarker, agentcapabilities.MCPReadmeErrorInventoryEndMarker)
if err != nil {
t.Fatal(err)
}
wantErrorBlock := agentcapabilities.MCPErrorCodeInventoryMarkdown(manifest)
if errorBlock != wantErrorBlock {
t.Fatalf("pulse-mcp README error-code block drifted from canonical manifest; run `go run ./scripts/generate-pulse-intelligence-docs.go`\n%s\nwant:\n%s", errorBlock, wantErrorBlock)
}
if strings.Contains(readme, "As of this writing") {
t.Fatal("pulse-mcp README must not frame the manifest-derived tool inventory as a hand-maintained snapshot")
}
}
func TestContract_AgentSubstrateDocReflectsCurrentMCPOnboarding(t *testing.T) {
source, err := os.ReadFile(filepath.Join("..", "..", "docs", "AGENT_SUBSTRATE.md"))
if err != nil {
t.Fatalf("read docs/AGENT_SUBSTRATE.md: %v", err)
}
doc := string(source)
for _, required := range []string{
"Settings -> API Access -> Agent integrations",
"client-ready `pulse-mcp` config snippets",
"manifest-owned MCP adapter setup contract",
"surface contract and affordance badges",
"server name, command",
"supported client config families",
"OpenCode's native `opencode.json` / `mcp` shape",
"common `mcpServers` shape for Claude-style clients",
"from the same adapter contract",
"shows both",
"OpenCode's native `opencode.json` shape",
"common `mcpServers`",
"`pulse-mcp` also has a published distribution path",
"`install-mcp.sh` and `install-mcp.ps1`",
"release installers",
"frontend-modern/src/components/Settings/AgentIntegrationsPanel.tsx",
} {
if !strings.Contains(doc, required) {
t.Errorf("docs/AGENT_SUBSTRATE.md must reflect current MCP onboarding; missing %q", required)
}
}
for _, forbidden := range []string{
"no Settings panel listing the declared capabilities",
"no \"generate MCP config snippet\" button",
"no token template that says \"create token for agent integration\"",
"A distribution path for `pulse-mcp`. Today an integrator must",
"clone the repo and `go build`",
"generates the reusable `pulse-mcp` server block",
} {
if strings.Contains(doc, forbidden) {
t.Errorf("docs/AGENT_SUBSTRATE.md must not preserve stale pre-onboarding gap copy; found %q", forbidden)
}
}
}
func TestContract_PulseIntelligenceOverviewPinsPatrolPrimaryCore(t *testing.T) {
source, err := os.ReadFile(filepath.Join("..", "..", "docs", "AI.md"))
if err != nil {
t.Fatalf("read docs/AI.md: %v", err)
}
doc := string(source)
manifest := agentcapabilities.CanonicalManifest()
overviewBlock, err := markedContractBlock(doc, agentcapabilities.PulseIntelligenceOverviewStartMarker, agentcapabilities.PulseIntelligenceOverviewEndMarker)
if err != nil {
t.Fatal(err)
}
wantOverviewBlock := agentcapabilities.PulseIntelligenceOverviewMarkdown(manifest.SurfaceContract)
if overviewBlock != wantOverviewBlock {
t.Fatalf("docs/AI.md Pulse Intelligence overview block drifted from canonical manifest:\n%s\nwant:\n%s", overviewBlock, wantOverviewBlock)
}
normalized := strings.Join(strings.Fields(doc), " ")
for _, required := range []string{
"Pulse Intelligence Core",
"Canonical context, governed actions, safety gates, approval state, action audit, and verification",
"Patrol as the primary built-in operator",
"Assistant plus MCP as access paths",
"Patrol is the first-party operations surface: it checks infrastructure, investigates issues, follows the chosen Patrol mode before acting, verifies outcomes, and records what happened",
"Pulse Assistant",
"Pulse MCP",
"contextual explanation, approval, and handoff surface for Patrol findings",
"Pulse Assistant and `pulse-mcp` are sibling surfaces over Pulse Intelligence, not competing implementations, and neither replaces the other",
"Assistant remains the in-app Pro surface",
"New operational capabilities should be added to the canonical API manifest first",
"MCP-only actions and Assistant-only copies of the same business logic are drift",
} {
if !strings.Contains(normalized, required) {
t.Errorf("docs/AI.md must pin Pulse Intelligence Core Patrol-primary product framing; missing %q", required)
}
}
for _, forbidden := range []string{
"three supported operator-facing ways",
"three supported surfaces",
"two supported operator-facing surfaces",
"proactive detection and investigation engine running on the shared Pulse Intelligence Core",
"Pulse Assistant should be replaced by",
"MCP replaces Pulse Assistant",
"Assistant can be removed",
"hand-maintained Assistant tool table",
} {
if strings.Contains(normalized, forbidden) {
t.Errorf("docs/AI.md must not drift away from the shared-core/Patrol-primary strategy; found %q", forbidden)
}
}
}
func TestContract_AgentParadigmDocReflectsGenericMCPOnboarding(t *testing.T) {
source, err := os.ReadFile(filepath.Join("..", "..", "docs", "releases", "AGENT_PARADIGM.md"))
if err != nil {
t.Fatalf("read docs/releases/AGENT_PARADIGM.md: %v", err)
}
doc := string(source)
for _, required := range []string{
"Claude Desktop, Claude Code, OpenCode, other MCP clients",
"client-ready MCP config snippets",
"OpenCode's native\n `opencode.json` / `mcp` shape",
"Drivable from MCP clients in one command",
"Wire it into any MCP-speaking client",
"OpenCode-native `mcp` block",
"common\n `mcpServers` block",
"manifest `requiredScopes`",
"read-only subset",
} {
if !strings.Contains(doc, required) {
t.Errorf("docs/releases/AGENT_PARADIGM.md must reflect generic MCP onboarding; missing %q", required)
}
}
for _, forbidden := range []string{
"Claude Desktop / Claude Code",
"Drivable from Claude in one command",
"Wire it into Claude Desktop or Claude Code",
"adapter for Claude Desktop and Claude Code",
"clients that accept\n `mcpServers`",
"`monitoring:read` (and",
} {
if strings.Contains(doc, forbidden) {
t.Errorf("docs/releases/AGENT_PARADIGM.md must not preserve stale Claude-only or partial-scope copy; found %q", forbidden)
}
}
}
func markedContractBlock(content, startMarker, endMarker string) (string, error) {
start := strings.Index(content, startMarker)
if start < 0 {
return "", fmt.Errorf("missing marker %s", startMarker)
}
bodyStart := start + len(startMarker)
end := strings.Index(content[bodyStart:], endMarker)
if end < 0 {
return "", fmt.Errorf("missing marker %s", endMarker)
}
return strings.TrimSpace(content[bodyStart : bodyStart+end]), nil
}
func TestContract_AssistantProviderSeamsDoNotUseMCPTerminology(t *testing.T) {
chatServiceSource, err := os.ReadFile(filepath.Join("..", "ai", "chat", "service.go"))
if err != nil {
t.Fatalf("read internal/ai/chat/service.go: %v", err)
}
chatService := string(chatServiceSource)
for _, fragment := range []string{
`AssistantAlertProvider = tools.AlertProvider`,
`AssistantFindingsProvider = tools.FindingsProvider`,
`AssistantDiscoveryProvider = tools.DiscoveryProvider`,
`AssistantUnifiedResourceProvider = tools.UnifiedResourceProvider`,
`func (s *Service) ExecuteAssistantTool(ctx context.Context, toolName string, args map[string]interface{}) (string, error)`,
`Msg("Executing Assistant registry tool directly")`,
} {
if !strings.Contains(chatService, fragment) {
t.Errorf("native Assistant provider seam must be Assistant-owned, missing %s", fragment)
}
}
for _, fragment := range []string{
`MCPAlertProvider`,
`MCPFindingsProvider`,
`MCPDiscoveryProvider`,
`MCPUnifiedResourceProvider`,
`func (s *Service) ExecuteMCPTool(`,
`MCP provider type aliases`,
`Msg("Executing MCP tool directly")`,
} {
if strings.Contains(chatService, fragment) {
t.Errorf("native Assistant provider seam must not be named as the MCP adapter surface; found %s", fragment)
}
}
apiHandlerSource, err := os.ReadFile("ai_handler.go")
if err != nil {
t.Fatalf("read internal/api/ai_handler.go: %v", err)
}
apiHandler := string(apiHandlerSource)
for _, fragment := range []string{
`SetAlertProvider(provider chat.AssistantAlertProvider)`,
`SetFindingsProvider(provider chat.AssistantFindingsProvider)`,
`SetDiscoveryProvider(provider chat.AssistantDiscoveryProvider)`,
`SetUnifiedResourceProvider(provider chat.AssistantUnifiedResourceProvider)`,
} {
if !strings.Contains(apiHandler, fragment) {
t.Errorf("API chat service interface must expose Assistant provider seams; missing %s", fragment)
}
}
for _, fragment := range []string{
`chat.MCPAlertProvider`,
`chat.MCPFindingsProvider`,
`chat.MCPDiscoveryProvider`,
`chat.MCPUnifiedResourceProvider`,
`for MCP tools`,
} {
if strings.Contains(apiHandler, fragment) {
t.Errorf("API chat service interface must not describe native Assistant providers as MCP tools; found %s", fragment)
}
}
settingsHandlerSource, err := os.ReadFile("ai_handlers.go")
if err != nil {
t.Fatalf("read internal/api/ai_handlers.go: %v", err)
}
settingsHandler := string(settingsHandlerSource)
for _, fragment := range []string{
`Used by Router to update Assistant tool visibility without restarting AI chat.`,
`Update Assistant control settings if control level or protected guests changed.`,
`This updates tool visibility without restarting AI chat.`,
} {
if !strings.Contains(settingsHandler, fragment) {
t.Errorf("AI settings handler must describe control refresh as native Assistant tool visibility; missing %s", fragment)
}
}
for _, fragment := range []string{
`Update MCP control settings`,
`MCP control settings`,
`MCP tool visibility`,
} {
if strings.Contains(settingsHandler, fragment) {
t.Errorf("AI settings handler must not describe native Assistant control refresh as MCP wiring; found %s", fragment)
}
}
routerSource, err := os.ReadFile("router.go")
if err != nil {
t.Fatalf("read internal/api/router.go: %v", err)
}
router := string(routerSource)
for _, fragment := range []string{
`ai.NewFindingsToolAdapter(findingsStore)`,
`tools.NewAlertManagerToolAdapter(alertManager)`,
`tools.NewDiscoveryToolAdapter(adapter)`,
`NewAssistantAgentProfileManager(persistence, licenseSvc)`,
`AI chat Assistant tool providers wired`,
`Wire control settings change callback to update Assistant tool visibility`,
} {
if !strings.Contains(router, fragment) {
t.Errorf("router must wire native Assistant providers without MCP terminology; missing %s", fragment)
}
}
for _, fragment := range []string{
`ai.NewFindingsMCPAdapter(findingsStore)`,
`tools.NewAlertManagerMCPAdapter(alertManager)`,
`tools.NewDiscoveryMCPAdapter(adapter)`,
`NewMCPAgentProfileManager(persistence, licenseSvc)`,
`AI chat MCP tool providers wired`,
`org-scoped MCP tool providers`,
} {
if strings.Contains(router, fragment) {
t.Errorf("router must not describe native Assistant provider wiring as MCP adapter wiring; found %s", fragment)
}
}
relaySource, err := os.ReadFile("router_routes_ai_relay.go")
if err != nil {
t.Fatalf("read internal/api/router_routes_ai_relay.go: %v", err)
}
relay := string(relaySource)
for _, fragment := range []string{
`toolAdapter := &assistantToolAdapter{handler: h}`,
`AssistantToolExecutor: toolAdapter`,
`type assistantToolAdapter struct`,
`var _ aicontracts.ApprovedAssistantToolExecutor = (*assistantToolAdapter)(nil)`,
`func (m *assistantToolAdapter) ExecuteApprovedAssistantTool(ctx context.Context, command, approvalID string) (string, int, error)`,
} {
if !strings.Contains(relay, fragment) {
t.Errorf("approved Assistant tool adapter must expose native Assistant execution from the current runtime; missing %s", fragment)
}
}
if !strings.Contains(relay, `chatService.ExecuteAssistantTool(ctx, params.Name, params.Arguments)`) {
t.Error("approved Assistant tool adapter must delegate to the native Assistant tool executor")
}
if strings.Contains(relay, `type mcpToolAdapter struct`) || strings.Contains(relay, `&mcpToolAdapter{`) {
t.Error("approved Assistant tool adapter must not be named as the MCP adapter surface")
}
for _, fragment := range []string{
`MCPExecutor: toolAdapter`,
`MCPExecutor: toolAdapter`,
`var _ aicontracts.MCPToolExecutor = (*assistantToolAdapter)(nil)`,
`func (m *assistantToolAdapter) ExecuteMCPTool(ctx context.Context, command, approvalID string) (string, int, error)`,
`chatService.ExecuteMCPTool(`,
} {
if strings.Contains(relay, fragment) {
t.Errorf("current runtime must not expose native Assistant execution through the legacy MCP executor seam; found %s", fragment)
}
}
contractSource, err := os.ReadFile(filepath.Join("..", "..", "pkg", "aicontracts", "fix_execution.go"))
if err != nil {
t.Fatalf("read pkg/aicontracts/fix_execution.go: %v", err)
}
contracts := string(contractSource)
for _, fragment := range []string{
`type ApprovedAssistantToolExecutor interface`,
`ExecuteApprovedAssistantTool(ctx context.Context, command, approvalID string) (output string, exitCode int, err error)`,
} {
if !strings.Contains(contracts, fragment) {
t.Errorf("cross-repo approved tool contract must expose native Assistant executor only; missing %s", fragment)
}
}
for _, fragment := range []string{
`type MCPToolExecutor interface`,
`ExecuteMCPTool(ctx context.Context, command, approvalID string) (output string, exitCode int, err error)`,
`Prefer ApprovedAssistantToolExecutor for new integrations`,
} {
if strings.Contains(contracts, fragment) {
t.Errorf("cross-repo approved tool contract must not preserve legacy MCP executor compatibility; found %s", fragment)
}
}
extensionSource, err := os.ReadFile(filepath.Join("..", "..", "pkg", "extensions", "ai_autofix.go"))
if err != nil {
t.Fatalf("read pkg/extensions/ai_autofix.go: %v", err)
}
extensionsSrc := string(extensionSource)
for _, fragment := range []string{
`AssistantToolExecutor aicontracts.ApprovedAssistantToolExecutor`,
`func (d AIAutoFixHandlerDeps) ResolveApprovedAssistantToolExecutor() aicontracts.ApprovedAssistantToolExecutor`,
`return d.AssistantToolExecutor`,
} {
if !strings.Contains(extensionsSrc, fragment) {
t.Errorf("auto-fix extension deps must expose native Assistant tool execution without MCP compatibility; missing %s", fragment)
}
}
for _, fragment := range []string{
`MCPExecutor`,
`MCPToolExecutor`,
`legacyMCPApprovedAssistantToolExecutor`,
`ExecuteMCPTool`,
`compatibility fallback for enterprise binders`,
} {
if strings.Contains(extensionsSrc, fragment) {
t.Errorf("auto-fix extension deps must not expose legacy MCP executor compatibility; found %s", fragment)
}
}
findingsAdapterSource, err := os.ReadFile(filepath.Join("..", "ai", "findings_tools_adapter.go"))
if err != nil {
t.Fatalf("read internal/ai/findings_tools_adapter.go: %v", err)
}
findingsAdapter := string(findingsAdapterSource)
if !strings.Contains(findingsAdapter, `type FindingsToolAdapter struct`) ||
!strings.Contains(findingsAdapter, `func NewFindingsToolAdapter(store *FindingsStore) *FindingsToolAdapter`) {
t.Error("findings store adapter must be named for the native tool registry")
}
if strings.Contains(findingsAdapter, `FindingsMCPAdapter`) || strings.Contains(findingsAdapter, `NewFindingsMCPAdapter`) {
t.Error("findings store adapter must not be named as an MCP adapter")
}
toolAdaptersSource, err := os.ReadFile(filepath.Join("..", "ai", "tools", "adapters.go"))
if err != nil {
t.Fatalf("read internal/ai/tools/adapters.go: %v", err)
}
toolAdapters := string(toolAdaptersSource)
for _, fragment := range []string{
`type AlertManagerToolAdapter struct`,
`func NewAlertManagerToolAdapter(manager AlertManager) *AlertManagerToolAdapter`,
`type RecoveryPointsToolAdapter struct`,
`func NewRecoveryPointsToolAdapter(manager *recoverymanager.Manager, orgID string) *RecoveryPointsToolAdapter`,
`type DiscoveryToolAdapter struct`,
`func NewDiscoveryToolAdapter(source DiscoverySource) *DiscoveryToolAdapter`,
} {
if !strings.Contains(toolAdapters, fragment) {
t.Errorf("Assistant runtime adapters must be named for the native tool registry; missing %s", fragment)
}
}
for _, fragment := range []string{
`MCPAdapter`,
`NewAlertManagerMCPAdapter`,
`NewRecoveryPointsMCPAdapter`,
`NewDiscoveryMCPAdapter`,
`MCP tools`,
`mcp.`,
} {
if strings.Contains(toolAdapters, fragment) {
t.Errorf("Assistant runtime adapters must not be named as MCP adapters; found %s", fragment)
}
}
intelligenceAdaptersSource, err := os.ReadFile(filepath.Join("..", "ai", "adapters", "adapters.go"))
if err != nil {
t.Fatalf("read internal/ai/adapters/adapters.go: %v", err)
}
intelligenceAdapters := string(intelligenceAdaptersSource)
for _, fragment := range []string{
`type IncidentRecorderToolAdapter struct`,
`func NewIncidentRecorderToolAdapter(recorder IncidentRecorderSource) *IncidentRecorderToolAdapter`,
`type EventCorrelatorToolAdapter struct`,
`func NewEventCorrelatorToolAdapter(correlator EventCorrelatorSource) *EventCorrelatorToolAdapter`,
} {
if !strings.Contains(intelligenceAdapters, fragment) {
t.Errorf("Assistant intelligence adapters must be named for the native tool registry; missing %s", fragment)
}
}
if strings.Contains(intelligenceAdapters, `MCPAdapter`) || strings.Contains(intelligenceAdapters, `NewIncidentRecorderMCPAdapter`) || strings.Contains(intelligenceAdapters, `NewEventCorrelatorMCPAdapter`) {
t.Error("Assistant intelligence adapters must not be named as MCP adapters")
}
agentProfilesSource, err := os.ReadFile("agent_profiles_tools.go")
if err != nil {
t.Fatalf("read internal/api/agent_profiles_tools.go: %v", err)
}
agentProfiles := string(agentProfilesSource)
if !strings.Contains(agentProfiles, `type AssistantAgentProfileManager struct`) ||
!strings.Contains(agentProfiles, `func NewAssistantAgentProfileManager(persistence *config.ConfigPersistence, licenseService licenseFeatureChecker) *AssistantAgentProfileManager`) {
t.Error("agent profile manager must be named for the native Assistant tool registry")
}
if strings.Contains(agentProfiles, `MCPAgentProfileManager`) || strings.Contains(agentProfiles, `NewMCPAgentProfileManager`) || strings.Contains(agentProfiles, `MCP tools`) {
t.Error("agent profile manager must not be named as an MCP adapter")
}
}
// TestContract_PulseMCPAdapterProjectsAgentCapabilitiesManifest pins the
// external-agent adapter boundary: pulse-mcp must stay a projection of the
// canonical capabilities manifest, not a second action registry.
func TestContract_PulseMCPAdapterProjectsAgentCapabilitiesManifest(t *testing.T) {
source, err := os.ReadFile(filepath.Join("..", "..", "cmd", "pulse-mcp", "main.go"))
if err != nil {
t.Fatalf("read cmd/pulse-mcp/main.go: %v", err)
}
src := string(source)
required := []string{
`internal/agentcapabilities`,
`type agentCapability = agentcapabilities.Capability`,
`type agentCapabilitiesManifest = agentcapabilities.Manifest`,
`type jsonRPCRequest = agentcapabilities.JSONRPCRequest`,
`agentcapabilities.ServeJSONRPCLines(`,
`agentcapabilities.WriteJSONRPCMessage(out, v)`,
`agentcapabilities.DispatchMCPToolServerRequest(ctx, *req, s.manifestToolServer().Handlers(func()`,
`agentcapabilities.MCPManifestToolServer{`,
`ServerName: pulseMCPServerName`,
`SurfaceID: agentcapabilities.SurfaceIDPulseMCP`,
`Manifest: manifest`,
`agentcapabilities.FetchManifest(context.Background(), manifestClient, *baseURL)`,
`agentcapabilities.StreamMCPEventNotifications(ctx, pulseMCPAgentSurfaceHTTPDoer{}, s.baseURL, path, s.token, func(notification agentcapabilities.JSONRPCRequest) error`,
}
for _, fragment := range required {
if !strings.Contains(src, fragment) {
t.Errorf("pulse-mcp must project the canonical agent capabilities manifest; missing %s", fragment)
}
}
forbidden := []string{
`type jsonRPCRequest struct`,
`type jsonRPCResponse struct`,
`type jsonRPCError struct`,
`type jsonRPCResponse = agentcapabilities.JSONRPCResponse`,
`type jsonRPCError = agentcapabilities.JSONRPCError`,
`bufio.NewScanner(in)`,
`scanner.Scan()`,
`agentcapabilities.DecodeJSONRPCRequest(`,
`agentcapabilities.NewJSONRPCParseErrorResponse(`,
`agentcapabilities.JSONRPCRequestExpectsResponse(`,
`json.Unmarshal([]byte(line)`,
`agentcapabilities.JSONRPCErrorParse`,
`len(req.ID) == 0`,
`string(req.ID) == "null"`,
`switch req.Method`,
`agentcapabilities.NewJSONRPCResponse(req.ID, nil)`,
`agentcapabilities.JSONRPCErrorInternal`,
`agentcapabilities.JSONRPCErrorMethodNotFound`,
`agentcapabilities.MCPToolServerHandlers{`,
`agentcapabilities.MCPMethodToolsList`,
`agentcapabilities.MCPMethodToolsCall`,
`agentcapabilities.MCPMethodResourcesList`,
`agentcapabilities.MCPMethodResourcesRead`,
`agentcapabilities.MCPMethodPromptsList`,
`agentcapabilities.MCPMethodPromptsGet`,
`agentcapabilities.MCPMethodPing`,
`func (s *mcpServer) handleInitialize(`,
`func (s *mcpServer) handleToolsList(`,
`func (s *mcpServer) handleToolsCall(`,
`func (s *mcpServer) handleResourcesList(`,
`func (s *mcpServer) handleResourcesRead(`,
`func (s *mcpServer) handlePromptsList(`,
`func (s *mcpServer) handlePromptsGet(`,
`agentcapabilities.NewMCPToolServerInitializeResult(pulseMCPServerName`,
`agentcapabilities.ProjectTools(s.manifest.Capabilities)`,
`agentcapabilities.ProjectMCPPrompts(s.manifest.Capabilities)`,
`agentcapabilities.GetMCPPrompt(s.manifest.Capabilities`,
`agentcapabilities.BuildMCPPrompt(s.manifest.Capabilities`,
`agentcapabilities.ExecuteMCPToolHTTP(`,
`agentcapabilities.ExecuteMCPManifestSurfaceToolHTTP(ctx, s.http, s.baseURL, s.token, s.manifest, agentcapabilities.SurfaceIDPulseMCP, raw)`,
`"protocolVersion": "2024-11-05"`,
`"protocolVersion": "2025-06-18"`,
`"isError": isError`,
`NewMCPHTTPTextResult(resp.Body, resp.StatusCode)`,
`agentcapabilities.MCPCapabilities{`,
`agentcapabilities.MCPToolsCapability{}`,
`agentcapabilities.MCPPulseNotificationsCapability{`,
`json.Unmarshal(raw, &params)`,
`fmt.Errorf("unknown tool: %s", params.Name)`,
`agentcapabilities.FindCapability(s.manifest.Capabilities, params.Name)`,
`agentcapabilities.CallCapabilityHTTP(ctx, s.http, s.baseURL, s.token, cap, params.Arguments)`,
`agentcapabilities.NewMCPCapabilityHTTPResult(resp)`,
`http.NewRequestWithContext(ctx, cap.Method`,
`httpReq.Header.Set("X-API-Token"`,
`baseURL+"/api/agent/capabilities"`,
`req.Header.Set("Accept", "text/event-stream")`,
`http.DefaultClient.Do(req)`,
`scanner := bufio.NewScanner(resp.Body)`,
`strings.HasPrefix(line, "event: ")`,
`strings.HasPrefix(line, "data: ")`,
`agentcapabilities.StreamAgentSSERecords(ctx, nil, s.baseURL, path, s.token`,
`agentcapabilities.IsTransportEventKind(event)`,
`agentcapabilities.NewMCPEventNotification(event, []byte(data))`,
`agentcapabilities.NewJSONRPCNotification(agentcapabilities.MCPNotificationMethod(event), params)`,
}
for _, fragment := range forbidden {
if strings.Contains(src, fragment) {
t.Errorf("pulse-mcp must not preserve local MCP wire/result envelope logic; found %s", fragment)
}
}
projectionSource, err := os.ReadFile(filepath.Join("..", "agentcapabilities", "projection.go"))
if err != nil {
t.Fatalf("read internal/agentcapabilities/projection.go: %v", err)
}
projection := string(projectionSource)
sharedRequired := []string{
`type ProjectedTool struct`,
`Title string`,
`OutputSchema json.RawMessage`,
`Annotations *ToolBehaviorHints`,
`Meta map[string]any`,
`type ToolBehaviorHints struct`,
`ToolMetaPulseCapabilityKey = "pulse.capability"`,
`ReadOnlyHint`,
`DestructiveHint`,
`IdempotentHint`,
`OpenWorldHint`,
`func ProjectTools(capabilities []Capability) []ProjectedTool`,
`func ProjectManifestSurfaceTools(manifest Manifest, surfaceID string) []ProjectedTool`,
`func ManifestSurfaceToolCapabilities(manifest Manifest, surfaceID string) []Capability`,
`func FindManifestSurfaceToolContract(manifest Manifest, surfaceID string) (SurfaceToolContract, bool)`,
`func ProjectTool(cap Capability) (ProjectedTool, bool)`,
`func CapabilityTitle(cap Capability) string`,
`func ToolAnnotations(cap Capability) *ToolBehaviorHints`,
`func ToolGovernanceBehaviorHints(governance ToolGovernanceDescriptor) *ToolBehaviorHints`,
`func CloneToolBehaviorHints(hints *ToolBehaviorHints) *ToolBehaviorHints`,
`func ToolMeta(cap Capability) map[string]any`,
`NormalizeCapabilityGovernance(cap)`,
`func FindCapability(capabilities []Capability, name string) (Capability, bool)`,
`type CapabilityLookupError struct`,
`func ResolveCapability(capabilities []Capability, name string) (Capability, error)`,
`func ResolveRequestResponseCapability(capabilities []Capability, name string) (Capability, error)`,
`return CloneCapability(cap), true`,
`IsRequestResponseCapability(cap)`,
`ResolutionNoteArgumentName = "resolution_note"`,
`NoteArgumentName = "note"`,
`Title: CapabilityTitle(cap)`,
`Description: ToolDescription(cap)`,
`InputSchema: ToolInputSchema(cap)`,
`OutputSchema: ToolOutputSchema(cap)`,
`Annotations: ToolAnnotations(cap)`,
`Meta: ToolMeta(cap)`,
`ToolMetaPulseCapabilityKey: capability`,
`return CloneRawMessage(cap.InputSchema)`,
`return CloneRawMessage(cap.OutputSchema)`,
}
for _, fragment := range sharedRequired {
if !strings.Contains(projection, fragment) {
t.Errorf("agentcapabilities must own the external-agent tool projection; missing %s", fragment)
}
}
for _, fragment := range []string{
`Annotations *MCPToolAnnotations`,
`type MCPToolAnnotations struct`,
`func ToolAnnotations(cap Capability) *MCPToolAnnotations`,
} {
if strings.Contains(projection, fragment) {
t.Errorf("external-agent tool projection must not make MCP the owner of shared behavior hints; found %s", fragment)
}
}
mcpAliasSource, err := os.ReadFile(filepath.Join("..", "agentcapabilities", "mcp.go"))
if err != nil {
t.Fatalf("read internal/agentcapabilities/mcp.go: %v", err)
}
mcp := string(mcpAliasSource)
if !strings.Contains(mcp, `type MCPToolAnnotations = ToolBehaviorHints`) {
t.Error("MCP adapter edge must keep the protocol-facing tool annotations alias over shared behavior hints")
}
schemaSource, err := os.ReadFile(filepath.Join("..", "agentcapabilities", "schema.go"))
if err != nil {
t.Fatalf("read internal/agentcapabilities/schema.go: %v", err)
}
schema := string(schemaSource)
for _, fragment := range []string{
`type ProviderTool struct`,
`type ProviderToolCall struct`,
`type ProviderToolResult struct`,
`func (t Tool) NormalizeCollections() Tool`,
`func (s InputSchema) NormalizeCollections() InputSchema`,
`func ClonePropertySchemas(properties map[string]PropertySchema) map[string]PropertySchema`,
`func CloneProviderInputSchema(schema map[string]interface{}) map[string]interface{}`,
`func CloneRawMessage(raw json.RawMessage) json.RawMessage`,
`func EmptyProviderTool() ProviderTool`,
`func (t ProviderTool) NormalizeCollections() ProviderTool`,
`t.InputSchema = CloneProviderInputSchema(t.InputSchema)`,
`t.BehaviorHints = CloneToolBehaviorHints(t.BehaviorHints)`,
`BehaviorHints *ToolBehaviorHints`,
`PulseGovernance *ToolGovernanceDescriptor`,
`func EmptyProviderToolCall() ProviderToolCall`,
`func (t ProviderToolCall) NormalizeCollections() ProviderToolCall`,
`t.Input = CloneToolArguments(t.Input)`,
`t.ThoughtSignature = CloneRawMessage(t.ThoughtSignature)`,
`type ProviderToolResultContextOptions struct`,
`type ProviderToolResultTruncation struct`,
`type ProviderToolResultContextProjection struct`,
`func NewProviderToolResult(toolUseID, content string, isError bool) ProviderToolResult`,
`func NewProviderToolErrorResult(toolUseID, content string) ProviderToolResult`,
`func NewProviderToolResultFromToolResult(toolUseID string, result ToolResult) ProviderToolResult`,
`func NewProviderToolResultContextProjection(toolUseID, content string, isError bool, opts ProviderToolResultContextOptions) ProviderToolResultContextProjection`,
`func NewProviderToolResultContextProjectionFromToolResult(toolUseID string, result ToolResult, opts ProviderToolResultContextOptions) ProviderToolResultContextProjection`,
`func ProviderToolResultModelContent(content string, opts ProviderToolResultContextOptions) (string, ProviderToolResultTruncation)`,
`func ProviderInputSchemaFromRaw(raw json.RawMessage) map[string]interface{}`,
`func ProjectProviderTool(tool Tool) ProviderTool`,
`func ProjectProviderToolWithGovernance(tool Tool, governance ToolGovernanceDescriptor) ProviderTool`,
`func ProjectProviderTools(tools []Tool) []ProviderTool`,
`func ProjectProviderToolsWithGovernance(tools []Tool, governance []ToolGovernanceDescriptor) []ProviderTool`,
`type AssistantProviderToolOptions struct`,
`func ProjectAssistantProviderTools(tools []Tool, governance []ToolGovernanceDescriptor, opts AssistantProviderToolOptions) []ProviderTool`,
`func ProjectPulseAssistantProviderTools(manifest Manifest, tools []Tool, governance []ToolGovernanceDescriptor, opts AssistantProviderToolOptions) []ProviderTool`,
`func ProviderToolGovernanceDescriptors(tools []ProviderTool) ([]ToolGovernanceDescriptor, bool)`,
`func AssistantNativeProviderTools() []ProviderTool`,
`func AssistantNativeProviderToolNames() []string`,
`func LegacyAssistantUtilityProviderTools() []ProviderTool`,
`LegacyAssistantRunCommandToolName = "run_command"`,
`LegacyAssistantFetchURLToolName = "fetch_url"`,
`LegacyAssistantSetResourceURLToolName = "set_resource_url"`,
`func ProviderToolNames(tools []ProviderTool) []string`,
`type ProviderToolNameCatalog struct`,
`func NewProviderToolNameCatalog(nameGroups ...[]string) ProviderToolNameCatalog`,
`func NewAssistantProviderToolNameCatalog(registryToolNames []string) ProviderToolNameCatalog`,
`func (c ProviderToolNameCatalog) Names() []string`,
`func (c ProviderToolNameCatalog) Has(name string) bool`,
`func (c ProviderToolNameCatalog) HasPrefix(prefix string) bool`,
`func ProviderToolDescriptionWithGovernance(description string, governance ToolGovernanceDescriptor) string`,
`func ProjectProviderToolCallToToolCall(tc ProviderToolCall) ToolCallParams`,
`func NormalizeProviderToolCallForExecution(tc ProviderToolCall) ProviderToolCall`,
`func NormalizeProviderToolCallsForExecution(calls []ProviderToolCall) []ProviderToolCall`,
`func NewPulseQuestionProviderTool() ProviderTool`,
`func PulseQuestionProviderInputSchema() map[string]interface{}`,
`func ParseProviderToolInput(raw string) (map[string]interface{}, bool)`,
`func ProviderToolInputOrRaw(raw string) map[string]interface{}`,
`schema = schema.NormalizeCollections()`,
`projected := CloneProviderInputSchema(schema)`,
`property = property.NormalizeCollections()`,
`tool = tool.NormalizeCollections()`,
`InputSchema: ProviderInputSchema(tool.InputSchema)`,
`ToolGovernancePromptDescription(governance)`,
`Pulse governance: `,
`projected.BehaviorHints = ToolGovernanceBehaviorHints(governance)`,
`projected.PulseGovernance = &governance`,
`return NewProviderToolResult(toolUseID, interpreted.Text, interpreted.IsError)`,
`ProviderToolResultModelContent(content, opts)`,
`return NormalizeToolCallParams(ToolCallParams{`,
`Arguments: tc.Input,`,
`params := ProjectProviderToolCallToToolCall(tc)`,
`normalized.Name = params.Name`,
`normalized.Input = params.Arguments`,
`projected = append(projected, AssistantNativeProviderTools()...)`,
`ManifestSurfaceAffordances(manifest, SurfaceIDPulseAssistant)`,
`if !affordances.Tools`,
`if !affordances.InteractiveQuestions`,
`return ProviderToolNames(AssistantNativeProviderTools())`,
`Name: PulseQuestionToolName`,
`InputSchema: PulseQuestionProviderInputSchema(),`,
`Name: LegacyAssistantRunCommandToolName`,
`Name: LegacyAssistantFetchURLToolName`,
`Name: LegacyAssistantSetResourceURLToolName`,
`type PulseQuestionToolType string`,
`PulseQuestionToolTypeText PulseQuestionToolType = "text"`,
`PulseQuestionToolTypeSelect PulseQuestionToolType = "select"`,
`func PulseQuestionToolTypeValues() []string`,
`func NormalizePulseQuestionToolType(rawType string, hasOptions bool) (PulseQuestionToolType, error)`,
`type PulseQuestionToolQuestion struct`,
`type PulseQuestionToolOption struct`,
`func ParsePulseQuestionToolInput(input map[string]interface{}) ([]PulseQuestionToolQuestion, error)`,
`func parsePulseQuestionToolOptions(rawOptions json.RawMessage) ([]PulseQuestionToolOption, error)`,
`"enum": PulseQuestionToolTypeValues(),`,
`return map[string]interface{}{"raw": raw}`,
} {
if !strings.Contains(schema, fragment) {
t.Errorf("agentcapabilities must own the Assistant provider tool projection; missing %s", fragment)
}
}
for _, fragment := range []string{
`func ProjectProviderToolCallToMCP(tc ProviderToolCall) MCPCallToolParams`,
} {
if strings.Contains(schema, fragment) {
t.Errorf("neutral provider schema must not own MCP compatibility aliases; found %s", fragment)
}
}
providerArtifactSource, err := os.ReadFile(filepath.Join("..", "agentcapabilities", "provider_tool_artifacts.go"))
if err != nil {
t.Fatalf("read internal/agentcapabilities/provider_tool_artifacts.go: %v", err)
}
providerArtifacts := string(providerArtifactSource)
for _, fragment := range []string{
`func ProviderToolCallArtifactIndex(content string, catalog ProviderToolNameCatalog) int`,
`func SplitTrailingProviderToolNamePrefix(content string, catalog ProviderToolNameCatalog) (visible string, held string)`,
`providerJSONToolCallLeakIndex(content, catalog)`,
`providerPlainFunctionToolCallLeakIndex(content, catalog)`,
`catalog.Has(name)`,
`catalog.HasPrefix(token)`,
} {
if !strings.Contains(providerArtifacts, fragment) {
t.Errorf("agentcapabilities must own provider tool-call artifact detection; missing %s", fragment)
}
}
mcpSource, err := os.ReadFile(filepath.Join("..", "agentcapabilities", "mcp.go"))
if err != nil {
t.Fatalf("read internal/agentcapabilities/mcp.go: %v", err)
}
mcpWire := string(mcpSource)
toolCallSource, err := os.ReadFile(filepath.Join("..", "agentcapabilities", "tool_call.go"))
if err != nil {
t.Fatalf("read internal/agentcapabilities/tool_call.go: %v", err)
}
toolNameSource, err := os.ReadFile(filepath.Join("..", "agentcapabilities", "tool_names.go"))
if err != nil {
t.Fatalf("read internal/agentcapabilities/tool_names.go: %v", err)
}
toolResultSource, err := os.ReadFile(filepath.Join("..", "agentcapabilities", "tool_result.go"))
if err != nil {
t.Fatalf("read internal/agentcapabilities/tool_result.go: %v", err)
}
toolExecutionSource, err := os.ReadFile(filepath.Join("..", "agentcapabilities", "tool_execution.go"))
if err != nil {
t.Fatalf("read internal/agentcapabilities/tool_execution.go: %v", err)
}
toolCore := string(toolNameSource) + "\n" + string(toolCallSource) + "\n" + string(toolResultSource) + "\n" + string(toolExecutionSource) + "\n" + projection
mcpWireContract := mcpWire + "\n" + toolCore
toolCallSrc := string(toolCallSource)
toolNameSrc := string(toolNameSource)
for _, pair := range []struct {
name string
value string
}{
{`PulseQueryToolName`, `"pulse_query"`},
{`PulseControlToolName`, `"pulse_control"`},
{`PulseReadToolName`, `"pulse_read"`},
{`PulseSummarizeToolName`, `"pulse_summarize"`},
{`PulseRunCommandToolName`, `"pulse_run_command"`},
{`PatrolGetFindingsToolName`, `"patrol_get_findings"`},
{`PatrolReportFindingToolName`, `"patrol_report_finding"`},
{`PatrolResolveFindingToolName`, `"patrol_resolve_finding"`},
} {
if !strings.Contains(toolNameSrc, pair.name) || !strings.Contains(toolNameSrc, pair.value) {
t.Errorf("agentcapabilities must own stable Pulse Intelligence tool identity %s=%s", pair.name, pair.value)
}
}
for _, fragment := range []string{
`type ToolCallKind int`,
`ToolCallKindResolve`,
`ToolCallKindRead`,
`ToolCallKindWrite`,
`ToolCallKindUserInput`,
`func (k ToolCallKind) String() string`,
`func ClassifyToolCall(toolName string, args map[string]interface{}) ToolCallKind`,
`case PulseQuestionToolName:`,
`case PulseQueryToolName, PulseDiscoveryToolName:`,
`case PulseControlToolName:`,
`case PulseReadToolName, LegacyAssistantFetchURLToolName:`,
`LegacyAssistantRunCommandToolName, LegacyAssistantSetResourceURLToolName`,
`return ToolCallKindWrite`,
} {
if !strings.Contains(toolCallSrc, fragment) {
t.Errorf("agentcapabilities must own shared tool-call safety classification; missing %s", fragment)
}
}
for _, fragment := range []string{
`type JSONRPCRequest struct`,
`type JSONRPCResponse struct`,
`type JSONRPCError struct`,
`type MCPInitializeResult struct`,
`type ToolCallParams struct`,
`type MCPCallToolParams = ToolCallParams`,
`func NormalizeToolCallParams(params ToolCallParams) ToolCallParams`,
`func ValidateToolCallParams(params ToolCallParams) error`,
`type MCPProjectedToolsResult struct`,
`type MCPResource struct`,
`type MCPListResourcesResult struct`,
`type MCPReadResourceParams struct`,
`type MCPReadResourceResult struct`,
`type MCPResourceContent struct`,
`type MCPPrompt struct`,
`Title string`,
`type MCPPromptArgument struct`,
`type MCPListPromptsResult struct`,
`type MCPGetPromptParams struct`,
`type MCPGetPromptResult struct`,
`type MCPPromptMessage struct`,
`type MCPCapabilities struct`,
`Instructions string`,
`Resources *MCPResourcesCapability`,
`Prompts *MCPPromptsCapability`,
`type MCPResourcesCapability struct`,
`type MCPPromptsCapability struct`,
`type ProjectedTool struct`,
`OutputSchema json.RawMessage`,
`Meta map[string]any`,
`type ToolResult struct`,
`StructuredContent map[string]any`,
`type MCPToolResult = ToolResult`,
`type ToolContent struct`,
`type MCPContent = ToolContent`,
`func (r MCPListResourcesResult) NormalizeCollections() MCPListResourcesResult`,
`func (r MCPReadResourceResult) NormalizeCollections() MCPReadResourceResult`,
`func (r MCPListPromptsResult) NormalizeCollections() MCPListPromptsResult`,
`func (r MCPGetPromptResult) NormalizeCollections() MCPGetPromptResult`,
`func EmptyToolResult() ToolResult`,
`func (r ToolResult) NormalizeCollections() ToolResult`,
`MCPPulseNotificationsExperimentalKey = "pulseNotifications"`,
`func NewMCPToolServerInitializeResult(`,
`func NewMCPManifestToolServerInitializeResult(`,
`func NewMCPManifestSurfaceToolServerInitializeResult(`,
`ManifestSurfaceAffordances(manifest, surfaceID)`,
`ResolveManifestSurfaceToolContract(manifest, surfaceID)`,
`affordances.Tools && hasSurfaceToolContract`,
`MCPManifestSurfacePromptProjectionSupported(manifest, surfaceID)`,
`Instructions: BuildPulseMCPOperatingInstructions(PulseMCPOperatingInstructionOptions{`,
`SupportsTools: exposeTools`,
`func MCPManifestResourceProjectionSupported(manifest Manifest, surfaceID string) bool`,
`func ManifestSurfaceResourceCapabilities(manifest Manifest, surfaceID string) []Capability`,
`func MCPManifestPromptProjectionSupported(manifest Manifest) bool`,
`func MCPManifestSurfacePromptProjectionSupported(manifest Manifest, surfaceID string) bool`,
`func ProjectMCPWorkflowPrompts(workflowPrompts []PulseWorkflowPrompt) []MCPPrompt`,
`func DecodeMCPGetPromptParams(raw json.RawMessage) (MCPGetPromptParams, error)`,
`func GetMCPPromptFromManifest(manifest Manifest, raw json.RawMessage) (MCPGetPromptResult, error)`,
`func GetMCPPromptFromManifestSurface(manifest Manifest, surfaceID string, raw json.RawMessage) (MCPGetPromptResult, error)`,
`func BuildMCPPromptFromManifest(manifest Manifest, params MCPGetPromptParams) (MCPGetPromptResult, error)`,
`ManifestPulseWorkflowPrompts(manifest)`,
`func MCPResourceURI(resourceID string) string`,
`func ResourceIDFromMCPResourceURI(rawURI string) (string, error)`,
`func DecodeMCPReadResourceParams(raw json.RawMessage) (MCPReadResourceParams, error)`,
`func ListMCPManifestSurfaceResourcesHTTP(`,
`func ReadMCPManifestSurfaceResourceHTTP(`,
`func resolveMCPManifestSurfaceResourceCapabilities(`,
`CallRequestResponseCapabilityHTTPBodyByName(ctx, client, baseURL, token, capabilities, FleetContextCapabilityName, map[string]any{})`,
`CallRequestResponseCapabilityHTTPBodyByName(ctx, client, baseURL, token, capabilities, ResourceContextCapabilityName, map[string]any{`,
`ResourceIDArgumentName: resourceID`,
`caps.Tools = &MCPToolsCapability{}`,
`MCPPulseNotificationsExperimentalKey: MCPPulseNotificationsCapability`,
`type MCPToolServerHandlers struct`,
`func DecodeJSONRPCRequest(`,
`fmt.Errorf("malformed JSON-RPC request: %w", err)`,
`func NewJSONRPCParseErrorResponse(`,
`func JSONRPCRequestExpectsResponse(`,
`type JSONRPCLineDispatcher func(context.Context, JSONRPCRequest) JSONRPCResponse`,
`type JSONRPCResponseWriter func(JSONRPCResponse) error`,
`func ServeJSONRPCLines(`,
`scanner.Buffer(make([]byte, 64*1024), 1<<22)`,
`DecodeJSONRPCRequest([]byte(line))`,
`NewJSONRPCParseErrorResponse(err)`,
`JSONRPCRequestExpectsResponse(req)`,
`func WriteJSONRPCMessage(`,
`enc.SetEscapeHTML(false)`,
`type MCPManifestToolServer struct`,
`SurfaceID string`,
`func (s MCPManifestToolServer) Handlers(`,
`func (s MCPManifestToolServer) Initialize() MCPInitializeResult`,
`func (s MCPManifestToolServer) ToolsList() MCPProjectedToolsResult`,
`func (s MCPManifestToolServer) ToolsCall(ctx context.Context, raw json.RawMessage) (MCPToolResult, error)`,
`func (s MCPManifestToolServer) ResourcesList(ctx context.Context) (MCPListResourcesResult, error)`,
`func (s MCPManifestToolServer) ResourcesRead(ctx context.Context, raw json.RawMessage) (MCPReadResourceResult, error)`,
`func (s MCPManifestToolServer) PromptsList() MCPListPromptsResult`,
`func (s MCPManifestToolServer) PromptsGet(ctx context.Context, raw json.RawMessage) (MCPGetPromptResult, error)`,
`return NewMCPManifestSurfaceToolServerInitializeResult(s.ServerName, s.ServerVersion, s.EmitPulseNotifications, s.Manifest, s.surfaceID())`,
`if !s.surfaceAffordances().Tools`,
`return MCPProjectedToolsResult{Tools: ProjectManifestSurfaceTools(s.Manifest, s.surfaceID())}`,
`return ExecuteMCPManifestSurfaceToolHTTP(ctx, s.Client, s.BaseURL, s.Token, s.Manifest, s.surfaceID(), raw)`,
`return ListMCPManifestSurfaceResourcesHTTP(ctx, s.Client, s.BaseURL, s.Token, s.Manifest, s.surfaceID())`,
`return ReadMCPManifestSurfaceResourceHTTP(ctx, s.Client, s.BaseURL, s.Token, s.Manifest, s.surfaceID(), raw)`,
`if !affordances.Resources`,
`fmt.Errorf("MCP resources are not enabled for surface %s", surfaceID)`,
`return (MCPListPromptsResult{Prompts: ProjectMCPWorkflowPrompts(ManifestPulseWorkflowPrompts(s.Manifest))}).NormalizeCollections()`,
`result, err := GetMCPPromptFromManifestSurface(s.Manifest, s.surfaceID(), raw)`,
`func (s MCPManifestToolServer) surfaceAffordances() SurfaceAffordanceContract`,
`func (s MCPManifestToolServer) surfaceID() string`,
`func normalizeMCPManifestSurfaceID(surfaceID string) string`,
`func DispatchMCPToolServerRequest(`,
`case MCPMethodInitialize:`,
`case MCPMethodToolsList:`,
`case MCPMethodToolsCall:`,
`case MCPMethodResourcesList:`,
`case MCPMethodResourcesRead:`,
`case MCPMethodPromptsList:`,
`case MCPMethodPromptsGet:`,
`return NewJSONRPCResponse(req.ID, result.NormalizeCollections())`,
`NewJSONRPCErrorResponse(req.ID, JSONRPCErrorInternal, err.Error(), nil)`,
`NewJSONRPCErrorResponse(req.ID, JSONRPCErrorMethodNotFound, fmt.Sprintf("method not found: %s", req.Method), nil)`,
`func DecodeMCPCallToolParams(`,
`params = NormalizeToolCallParams(params)`,
`ValidateToolCallParams(params)`,
`func PrepareToolRegistryExecution(name string, args map[string]any) (ToolCallParams, ToolResult, bool)`,
`func NewInvalidToolCallParamsResult(err error) ToolResult`,
`func NewUnknownToolResult(name string) ToolResult`,
`func NewControlToolsDisabledToolResult() ToolResult`,
`func ExecuteMCPManifestSurfaceToolHTTP(`,
`DecodeMCPCallToolParams(raw)`,
`ManifestSurfaceAffordances(manifest, surfaceID)`,
`ManifestSurfaceToolCapabilities(manifest, surfaceID)`,
`return ExecuteCapabilityToolHTTP(ctx, client, baseURL, token, ManifestSurfaceToolCapabilities(manifest, surfaceID), params)`,
`OutputSchema: ToolOutputSchema(cap)`,
`func ToolOutputSchema(cap Capability) json.RawMessage`,
`func ExecuteCapabilityToolHTTP(`,
`params = NormalizeToolCallParams(params)`,
`ValidateToolCallParams(params)`,
`CallRequestResponseCapabilityHTTPByName(ctx, client, baseURL, token, capabilities, params.Name, params.Arguments)`,
`var lookupErr CapabilityLookupError`,
`errors.As(err, &lookupErr)`,
`return NewCapabilityHTTPToolResult(resp), nil`,
`func NewToolTextContent(text string) ToolContent`,
`func NewToolTextResult(text string) ToolResult`,
`func NewToolErrorResult(err error) ToolResult`,
`func NewToolJSONResult(data any) ToolResult`,
`func NewToolJSONResultWithIsError(data any, isError bool) ToolResult`,
`func NewToolHTTPTextResult(`,
`func NewCapabilityHTTPToolResult(`,
`func ToolStructuredContentFromJSON(`,
`func CloneToolStructuredContent(`,
`func ToolResultText(result ToolResult) string`,
`type ToolResultInterpretation struct`,
`func InterpretToolResult(result ToolResult) ToolResultInterpretation`,
`ApprovalRequired: HasApprovalRequiredToolMarker(text)`,
`PolicyBlocked: HasPolicyBlockedToolMarker(text)`,
`type DirectToolExecutionOptions struct`,
`type DirectToolExecutionOutcome struct`,
`func InterpretDirectToolExecution(result ToolResult, opts DirectToolExecutionOptions) (DirectToolExecutionOutcome, error)`,
`resp.OK()`,
`func NewJSONRPCNotification(`,
`func NewMCPEventNotification(`,
`const (`,
`MCPProtocolVersion = "2025-06-18"`,
`MCPMethodResourcesList = "resources/list"`,
`MCPMethodResourcesRead = "resources/read"`,
`MCPMethodPromptsList = "prompts/list"`,
`MCPMethodPromptsGet = "prompts/get"`,
`MCPResourceContextURIHost = "resource"`,
`MCPResourceContextMIMEType = "application/json"`,
} {
if !strings.Contains(mcpWireContract, fragment) {
t.Errorf("agentcapabilities must own the shared MCP wire/result envelope; missing %s", fragment)
}
}
for _, fragment := range []string{
`func MCPResourceProjectionSupported(capabilities []Capability) bool`,
`func ListMCPResourcesHTTP(ctx context.Context, client HTTPDoer, baseURL, token string, capabilities []Capability)`,
`func ReadMCPResourceHTTP(ctx context.Context, client HTTPDoer, baseURL, token string, capabilities []Capability`,
`return ListMCPResourcesHTTP(ctx, s.Client, s.BaseURL, s.Token, s.Manifest.Capabilities)`,
`return ReadMCPResourceHTTP(ctx, s.Client, s.BaseURL, s.Token, s.Manifest.Capabilities, raw)`,
} {
if strings.Contains(mcpWireContract, fragment) {
t.Errorf("shared MCP resource projection must be manifest-surface-owned, not raw-capability-owned; found %s", fragment)
}
}
for _, fragment := range []string{
`func MCPPromptProjectionSupported(capabilities []Capability) bool`,
`func ProjectMCPPrompts(capabilities []Capability) []MCPPrompt`,
`func GetMCPPrompt(capabilities []Capability, raw json.RawMessage)`,
`func BuildMCPPrompt(capabilities []Capability, params MCPGetPromptParams)`,
`return GetMCPPromptFromManifest(s.Manifest, raw)`,
} {
if strings.Contains(mcpWireContract, fragment) {
t.Errorf("shared MCP prompt projection must be manifest-owned, not raw-capability-owned; found %s", fragment)
}
}
for _, fragment := range []string{
`type ToolCallParams struct`,
`func NormalizeToolCallParams(params ToolCallParams) ToolCallParams`,
`func ValidateToolCallParams(params ToolCallParams) error`,
`func PrepareToolRegistryExecution(name string, args map[string]any) (ToolCallParams, ToolResult, bool)`,
`func NewInvalidToolCallParamsResult(err error) ToolResult`,
`func NewUnknownToolResult(name string) ToolResult`,
`func NewControlToolsDisabledToolResult() ToolResult`,
`type ProjectedTool struct`,
`OutputSchema json.RawMessage`,
`Meta map[string]any`,
`type ToolContent struct`,
`type ToolResult struct`,
`StructuredContent map[string]any`,
`func EmptyToolResult() ToolResult`,
`func (r ToolResult) NormalizeCollections() ToolResult`,
`func NewToolTextContent(text string) ToolContent`,
`func NewToolTextResult(text string) ToolResult`,
`func NewToolErrorResult(err error) ToolResult`,
`func NewToolJSONResult(data any) ToolResult`,
`func NewToolHTTPTextResult(`,
`func NewCapabilityHTTPToolResult(`,
`func ToolOutputSchema(cap Capability) json.RawMessage`,
`func ToolStructuredContentFromJSON(`,
`func CloneToolStructuredContent(`,
`func ToolResultText(result ToolResult) string`,
`type ToolResultInterpretation struct`,
`func InterpretToolResult(result ToolResult) ToolResultInterpretation`,
`func ExecuteCapabilityToolHTTP(`,
`type DirectToolExecutionOptions struct`,
`type DirectToolExecutionOutcome struct`,
`func InterpretDirectToolExecution(result ToolResult, opts DirectToolExecutionOptions) (DirectToolExecutionOutcome, error)`,
} {
if strings.Contains(mcpWire, fragment) {
t.Errorf("neutral Pulse Intelligence tool primitive must not be implemented in mcp.go; found %s", fragment)
}
if !strings.Contains(toolCore, fragment) {
t.Errorf("neutral Pulse Intelligence tool primitive must live outside mcp.go; missing %s", fragment)
}
}
workflowPromptSource, err := os.ReadFile(filepath.Join("..", "agentcapabilities", "workflow_prompt.go"))
if err != nil {
t.Fatalf("read internal/agentcapabilities/workflow_prompt.go: %v", err)
}
workflowPromptCore := string(workflowPromptSource)
for _, fragment := range []string{
`PulseWorkflowPromptTriageFleet = "pulse_triage_fleet"`,
`PulseWorkflowPromptOperationsLoop`,
`MCPPromptTriageFleet = PulseWorkflowPromptTriageFleet`,
`MCPPromptOperationsLoop`,
`type PulseWorkflowPrompt struct`,
`type PulseWorkflowPromptArgument struct`,
`type PulseWorkflowPromptParams struct`,
`type PulseWorkflowPromptResult struct`,
`type PulseWorkflowPromptRenderOptions struct`,
`func ManifestPulseWorkflowPrompts(manifest Manifest) []PulseWorkflowPrompt`,
`func ProjectPulseWorkflowPrompts(capabilities []Capability) []PulseWorkflowPrompt`,
`func BuildPulseWorkflowPrompt(capabilities []Capability, params PulseWorkflowPromptParams) (PulseWorkflowPromptResult, error)`,
`func BuildPulseWorkflowPromptWithOptions(capabilities []Capability, params PulseWorkflowPromptParams, opts PulseWorkflowPromptRenderOptions) (PulseWorkflowPromptResult, error)`,
`func BuildPulseWorkflowPromptFromManifest(manifest Manifest, params PulseWorkflowPromptParams) (PulseWorkflowPromptResult, error)`,
`func BuildPulseWorkflowPromptFromManifestWithOptions(manifest Manifest, params PulseWorkflowPromptParams, opts PulseWorkflowPromptRenderOptions) (PulseWorkflowPromptResult, error)`,
`func RequiredPulseWorkflowPromptArgument(args map[string]string, name string) (string, error)`,
`Name: ResourceIDArgumentName`,
`RequiredPulseWorkflowPromptArgument(params.Arguments, ResourceIDArgumentName)`,
`ResourceIDArgumentName, resourceID`,
`Name: FindingIDArgumentName`,
`FindCapability(capabilities, ListFindingsCapabilityName)`,
`RequiredPulseWorkflowPromptArgument(params.Arguments, FindingIDArgumentName)`,
`ListFindingsCapabilityName`,
`PlanActionCapabilityName`,
`DecideActionCapabilityName`,
`ExecuteActionCapabilityName`,
`ResolveFindingCapabilityName`,
`Triage the Pulse fleet using the canonical fleet context capability`,
`Review one Patrol finding and propose the safest governed next step`,
`Patrol issue handling`,
`Do not execute write tools unless the operator explicitly asks for a governed action`,
} {
if !strings.Contains(workflowPromptCore, fragment) {
t.Errorf("neutral Pulse Intelligence workflow prompt contract must live outside mcp.go; missing %s", fragment)
}
}
for _, fragment := range []string{
`func PulseWorkflowPromptProjectionSupported(capabilities []Capability) bool`,
} {
if strings.Contains(workflowPromptCore, fragment) {
t.Errorf("shared workflow prompt projection must be manifest-owned, not raw-capability-owned; found %s", fragment)
}
}
for _, fragment := range []string{
`Pulse fleet triage`,
`Triage the Pulse fleet using the canonical fleet context capability`,
`Review one Patrol finding and propose the safest governed next step`,
`prompt argument %s is required`,
`func RequiredPulseWorkflowPromptArgument`,
} {
if strings.Contains(mcpWire, fragment) {
t.Errorf("MCP edge must not own neutral Pulse Intelligence workflow prompt behavior; found %s", fragment)
}
}
for _, fragment := range []string{
`func NormalizeMCPCallToolParams(`,
`func ValidateMCPCallToolParams(`,
`func ProjectProviderToolCallToMCP(`,
`func PrepareMCPToolRegistryExecution(`,
`func NewInvalidMCPToolCallParamsResult(`,
`func NewUnknownMCPToolResult(`,
`func NewControlToolsDisabledMCPToolResult(`,
`func EmptyMCPToolResult(`,
`func NewMCPTextContent(`,
`func NewMCPTextResult(`,
`func NewMCPErrorResult(`,
`func NewProviderToolResultFromMCP(`,
`func NewMCPToolResponseResult(`,
`func NewMCPJSONResult(`,
`func NewMCPJSONResultWithIsError(`,
`func NewMCPHTTPTextResult(`,
`func NewMCPCapabilityHTTPResult(`,
`func MCPToolResultText(`,
`type MCPToolResultInterpretation`,
`func InterpretMCPToolResult(`,
`type DirectMCPToolExecutionOptions`,
`type DirectMCPToolExecutionOutcome`,
`func InterpretDirectMCPToolExecution(`,
} {
if strings.Contains(mcpWire, fragment) {
t.Errorf("MCP edge must not preserve compatibility wrappers around neutral tool helpers; found %s", fragment)
}
}
sseSource, err := os.ReadFile(filepath.Join("..", "agentcapabilities", "sse.go"))
if err != nil {
t.Fatalf("read internal/agentcapabilities/sse.go: %v", err)
}
sse := string(sseSource)
for _, fragment := range []string{
`type SSERecord struct`,
`const AgentSSEAccept = "text/event-stream"`,
`func ScanSSERecords(`,
`scanner.Buffer(make([]byte, 64*1024), 1<<22)`,
`case "event":`,
`case "data":`,
`strings.Join(dataLines, "\n")`,
`func StreamAgentSSERecords(`,
`NewAgentHTTPRequest(ctx, http.MethodGet, baseURL, path, token, nil)`,
`req.Header.Set("Accept", AgentSSEAccept)`,
`httpClient(client).Do(req)`,
`ScanSSERecords(resp.Body, handle)`,
`func IsActionableSSERecord(`,
`func StreamAgentActionableSSERecords(`,
`type MCPEventNotificationWriter func(JSONRPCRequest) error`,
`func StreamMCPEventNotifications(`,
`StreamAgentActionableSSERecords(ctx, client, baseURL, path, token, func(record SSERecord) bool`,
`NewMCPEventNotification(record.Event, []byte(record.Data))`,
} {
if !strings.Contains(sse, fragment) {
t.Errorf("agentcapabilities must own the shared agent SSE parser, subscription transport, and MCP notification bridge; missing %s", fragment)
}
}
textInvocationSource, err := os.ReadFile(filepath.Join("..", "agentcapabilities", "text_tool_invocation.go"))
if err != nil {
t.Fatalf("read internal/agentcapabilities/text_tool_invocation.go: %v", err)
}
textInvocation := string(textInvocationSource)
for _, fragment := range []string{
`func IsTextToolInvocation(command string) bool`,
`func ParseTextToolInvocation(command string) (ToolCallParams, error)`,
`const ApprovalArgumentKey = "_approval_id"`,
`func ApprovalArgument(args map[string]any) string`,
`func IsInternalToolArgument(name string) bool`,
`func CloneToolArguments(args map[string]any) map[string]any`,
`func PublicToolArguments(args map[string]any) map[string]any`,
`func WithApprovalArgument(args map[string]any, approvalID string) map[string]any`,
`cloned[k] = cloneSchemaValue(v)`,
`public[k] = cloneSchemaValue(v)`,
`strings.TrimPrefix(command, "default_api:")`,
`args := make(map[string]any)`,
`ToolCallParams{Name: toolName, Arguments: args}`,
`ValidateToolCallParams(params)`,
`func splitTextToolArguments(argsStr string) []string`,
} {
if !strings.Contains(textInvocation, fragment) {
t.Errorf("agentcapabilities must own the shared text tool invocation parser; missing %s", fragment)
}
}
aiRelaySource, err := os.ReadFile("router_routes_ai_relay.go")
if err != nil {
t.Fatalf("read internal/api/router_routes_ai_relay.go: %v", err)
}
aiRelay := string(aiRelaySource)
for _, fragment := range []string{
`agentcapabilities.ParseTextToolInvocation(command)`,
`params.Arguments = agentcapabilities.WithApprovalArgument(params.Arguments, approvalID)`,
`chatService.ExecuteAssistantTool(ctx, params.Name, params.Arguments)`,
} {
if !strings.Contains(aiRelay, fragment) {
t.Errorf("AI relay MCP executor must consume the shared text tool invocation parser; missing %s", fragment)
}
}
for _, fragment := range []string{
`func parseMCPToolCall(`,
`func splitToolArgs(`,
`func isMCPToolCall(`,
`params.Arguments["_approval_id"]`,
} {
if strings.Contains(aiRelay, fragment) {
t.Errorf("AI relay must not preserve local text tool invocation parsing; found %s", fragment)
}
}
assistantToolFiles := []string{
filepath.Join("..", "ai", "chat", "agentic.go"),
filepath.Join("..", "ai", "tools", "tools_control.go"),
filepath.Join("..", "ai", "tools", "tools_docker.go"),
filepath.Join("..", "ai", "tools", "tools_file.go"),
filepath.Join("..", "ai", "tools", "tools_kubernetes.go"),
}
for _, path := range assistantToolFiles {
source, err := os.ReadFile(path)
if err != nil {
t.Fatalf("read %s: %v", path, err)
}
if strings.Contains(string(source), `["_approval_id"]`) {
t.Errorf("%s must consume shared approved-action argument helpers instead of local key indexing", path)
}
}
httpSource, err := os.ReadFile(filepath.Join("..", "agentcapabilities", "http.go"))
if err != nil {
t.Fatalf("read internal/agentcapabilities/http.go: %v", err)
}
agentHTTP := string(httpSource)
for _, fragment := range []string{
`const (`,
`AgentCapabilitiesPath = "/api/agent/capabilities"`,
`AgentAPITokenHeader = "X-API-Token"`,
`type HTTPCallResponse struct`,
`func NewAgentHTTPRequest(`,
`func FetchManifest(`,
`func BuildCapabilityHTTPRequest(`,
`func CallCapabilityHTTP(`,
`func CallCapabilityHTTPByName(`,
`func CallRequestResponseCapabilityHTTPByName(`,
`func (r HTTPCallResponse) FailureError() error`,
`ProjectCapabilityCall(cap, args)`,
`ResolveRequestResponseCapability(capabilities, name)`,
`ResolveCapability(capabilities, name)`,
`DecodeErrorEnvelope(r.Body)`,
} {
if !strings.Contains(agentHTTP, fragment) {
t.Errorf("agentcapabilities must own the shared agent HTTP substrate; missing %s", fragment)
}
}
if !strings.Contains(projection, `for k, v := range PublicToolArguments(args)`) {
t.Error("manifest-backed HTTP projection must strip internal tool-call arguments before building public request bodies")
}
toolResponseSource, err := os.ReadFile(filepath.Join("..", "agentcapabilities", "tool_response.go"))
if err != nil {
t.Fatalf("read internal/agentcapabilities/tool_response.go: %v", err)
}
toolResponse := string(toolResponseSource)
for _, fragment := range []string{
`type ToolResponse struct`,
`type ToolError struct`,
`ErrCodeFSMBlocked`,
`ErrCodeStrictResolution`,
`ErrCodeRoutingMismatch`,
`ErrCodeExecutionContextUnavailable`,
`func NewToolBlockedError(`,
`func NewToolResponseResult(resp ToolResponse) ToolResult`,
`func ToolResultErrorCode(resultText string) (string, bool)`,
`func ToolResultHasErrorCode(resultText, code string) bool`,
`func ToolResultHasVerificationOK(resultText string) bool`,
} {
if !strings.Contains(toolResponse, fragment) {
t.Errorf("agentcapabilities must own the shared tool response envelope; missing %s", fragment)
}
}
for _, fragment := range []string{
`func NewMCPToolResponseResult(`,
} {
if strings.Contains(toolResponse, fragment) {
t.Errorf("neutral tool response envelope must not own MCP compatibility wrappers; found %s", fragment)
}
}
agenticVerificationSource, err := os.ReadFile(filepath.Join("..", "ai", "chat", "agentic_verification.go"))
if err != nil {
t.Fatalf("read internal/ai/chat/agentic_verification.go: %v", err)
}
if strings.Contains(string(agenticVerificationSource), `func toolResultHasVerificationOK(`) {
t.Error("chat FSM verification evidence parsing must live in shared agentcapabilities, not a chat-local helper")
}
agenticSource, err := os.ReadFile(filepath.Join("..", "ai", "chat", "agentic.go"))
if err != nil {
t.Fatalf("read internal/ai/chat/agentic.go: %v", err)
}
if !strings.Contains(string(agenticSource), `agentcapabilities.ToolResultHasVerificationOK(resultText)`) {
t.Error("chat FSM self-verification must consume the shared tool-result verification parser")
}
if !strings.Contains(string(agenticSource), `agentcapabilities.ToolResultHasErrorCode(resultText, agentcapabilities.ErrCodeStrictResolution)`) {
t.Error("chat strict-resolution recovery must consume the shared tool-result error-code parser")
}
if strings.Contains(string(agenticSource), `strings.Contains(resultText, "STRICT_RESOLUTION")`) {
t.Error("chat strict-resolution recovery must not use local string matching")
}
if !strings.Contains(string(agenticSource), `agentcapabilities.ErrCodeFSMBlocked`) {
t.Error("chat FSM recovery tracking must consume the shared FSM-blocked error code")
}
if strings.Contains(string(agenticSource), `"FSM_BLOCKED"`) {
t.Error("chat FSM recovery tracking must not hard-code the FSM-blocked error code")
}
toolMarkerSource, err := os.ReadFile(filepath.Join("..", "agentcapabilities", "tool_marker.go"))
if err != nil {
t.Fatalf("read internal/agentcapabilities/tool_marker.go: %v", err)
}
toolMarker := string(toolMarkerSource)
for _, fragment := range []string{
`ToolMarkerApprovalRequiredPrefix = ErrCodeApprovalRequired + ":"`,
`ToolMarkerPolicyBlockedPrefix = ErrCodePolicyBlocked + ":"`,
`ToolMarkerApprovalRequiredType = "approval_required"`,
`ToolMarkerPolicyBlockedType = "policy_blocked"`,
`type ApprovalRequiredToolMarkerData struct`,
`func (d ApprovalRequiredToolMarkerData) TargetHint() string`,
`func (d ApprovalRequiredToolMarkerData) DescriptionText() string`,
`func ApprovalRequiredToolMarker(`,
`func PolicyBlockedToolMarker(`,
`func FormatApprovalRequiredToolMarker(`,
`func FormatPolicyBlockedToolMarker(`,
`func HasApprovalRequiredToolMarker(`,
`func HasPolicyBlockedToolMarker(`,
`func ApprovalRequiredToolMarkerPayloadJSON(`,
`func ParseApprovalRequiredToolMarkerPayload(`,
`func ParseApprovalRequiredToolMarkerData(`,
} {
if !strings.Contains(toolMarker, fragment) {
t.Errorf("agentcapabilities must own the shared Assistant tool marker vocabulary; missing %s", fragment)
}
}
controlLevelSource, err := os.ReadFile(filepath.Join("..", "agentcapabilities", "control_level.go"))
if err != nil {
t.Fatalf("read internal/agentcapabilities/control_level.go: %v", err)
}
controlLevel := string(controlLevelSource)
for _, fragment := range []string{
`type ControlLevel string`,
`ControlLevelReadOnly`,
`ControlLevelControlled`,
`ControlLevelAutonomous`,
`func NormalizeControlLevel(`,
`func IsValidControlLevel(`,
`func ControlLevelAllowsControlTools(`,
} {
if !strings.Contains(controlLevel, fragment) {
t.Errorf("agentcapabilities must own the shared control-level contract; missing %s", fragment)
}
}
typesSource, err := os.ReadFile(filepath.Join("..", "agentcapabilities", "types.go"))
if err != nil {
t.Fatalf("read internal/agentcapabilities/types.go: %v", err)
}
typesSrc := string(typesSource)
for _, fragment := range []string{
`const ControlToolsDisabledMessage = "Control tools are disabled.`,
`func NormalizeToolGovernance(`,
`func NormalizeCapabilityGovernance(cap Capability) ToolGovernance`,
`func CapabilityActionMode(cap Capability) ActionMode`,
`func NewToolGovernanceDescriptor(`,
`func NormalizeToolGovernanceDescriptor(descriptor ToolGovernanceDescriptor) ToolGovernanceDescriptor`,
`governance.ActionMode = ActionModeWrite`,
`CapabilityActionMode(cap)`,
`governance.ApprovalPolicy = ApprovalPolicyActionPlan`,
`DefaultApprovalPolicyDescription(governance.ApprovalPolicy)`,
`ToolGovernanceDescriptor{`,
} {
if !strings.Contains(typesSrc, fragment) {
t.Errorf("agentcapabilities must own shared tool-governance defaults; missing %s", fragment)
}
}
governancePromptSource, err := os.ReadFile(filepath.Join("..", "agentcapabilities", "governance_prompt.go"))
if err != nil {
t.Fatalf("read internal/agentcapabilities/governance_prompt.go: %v", err)
}
governancePromptSrc := string(governancePromptSource)
for _, fragment := range []string{
`type ToolGovernancePromptOptions struct`,
`type PulseIntelligenceOperatingInstructionOptions struct`,
`func BuildToolGovernancePromptSection(`,
`func BuildAssistantToolGovernancePromptSection(`,
`func BuildPulseIntelligenceOperatingInstructions() string`,
`func BuildPulseAssistantOperatingInstructions() string`,
`type PulseMCPOperatingInstructionOptions struct`,
`func BuildPulseMCPOperatingInstructions(`,
`func BuildPulseIntelligenceOperatingInstructionsForSurface(`,
`ResolveSurfaceAffordanceContract(`,
`func ToolGovernancePromptLine(`,
`func ToolGovernancePromptDescription(`,
`func AssistantGovernanceOfferedToolNames(`,
`AdditionalToolGovernanceLines []string`,
`const PulseQuestionToolName = "pulse_question"`,
`func PulseQuestionToolGovernancePromptLine() string`,
`SupportsInteractiveQuestions bool`,
`SurfaceAffordanceLabels(surfaceAffordancesFromOptions(opts))`,
`## PULSE INTELLIGENCE OPERATING MODEL`,
`No Pulse tools are offered for this turn.`,
`This manifest is generated from Pulse's governed tool registry.`,
`ApprovalPolicyScopeOnly`,
`ActionModeRead`,
} {
if !strings.Contains(governancePromptSrc, fragment) {
t.Errorf("agentcapabilities must own shared tool-governance prompt projection; missing %s", fragment)
}
}
for _, fragment := range []string{
`func CloneCapability(cap Capability) Capability`,
`func CloneCapabilities(capabilities []Capability) []Capability`,
`cap.InputSchema = CloneRawMessage(cap.InputSchema)`,
} {
if !strings.Contains(typesSrc, fragment) {
t.Errorf("agentcapabilities must own detached capability projection helpers; missing %s", fragment)
}
}
textToolInvocationSource, err := os.ReadFile(filepath.Join("..", "agentcapabilities", "text_tool_invocation.go"))
if err != nil {
t.Fatalf("read internal/agentcapabilities/text_tool_invocation.go: %v", err)
}
textToolInvocation := string(textToolInvocationSource)
for _, fragment := range []string{
`const CurrentResourceHandle = "current_resource"`,
`func IsCurrentResourceReference(value string) bool`,
`func ToolInputContainsCurrentResourceReference(value any) bool`,
`currentResourceReferenceAliases`,
`"redacted by policy"`,
} {
if !strings.Contains(textToolInvocation, fragment) {
t.Errorf("agentcapabilities must own shared current_resource tool-argument semantics; missing %s", fragment)
}
}
configSource, err := os.ReadFile(filepath.Join("..", "config", "ai.go"))
if err != nil {
t.Fatalf("read internal/config/ai.go: %v", err)
}
configAI := string(configSource)
for _, fragment := range []string{
`ControlLevelReadOnly = string(agentcapabilities.ControlLevelReadOnly)`,
`ControlLevelControlled = string(agentcapabilities.ControlLevelControlled)`,
`ControlLevelAutonomous = string(agentcapabilities.ControlLevelAutonomous)`,
`return string(agentcapabilities.NormalizeControlLevel(c.ControlLevel))`,
`return agentcapabilities.ControlLevelAllowsControlTools(agentcapabilities.ControlLevel(c.GetControlLevel()))`,
`return agentcapabilities.IsValidControlLevel(level)`,
} {
if !strings.Contains(configAI, fragment) {
t.Errorf("AI config must alias shared control-level vocabulary; missing %s", fragment)
}
}
assistantProtocol, err := os.ReadFile(filepath.Join("..", "ai", "tools", "protocol.go"))
if err != nil {
t.Fatalf("read internal/ai/tools/protocol.go: %v", err)
}
assistantSrc := string(assistantProtocol)
for _, fragment := range []string{
`type Tool = agentcapabilities.Tool`,
`type CallToolResult = agentcapabilities.ToolResult`,
`type Content = agentcapabilities.ToolContent`,
`type CallToolParams = agentcapabilities.ToolCallParams`,
`type ToolResponse = agentcapabilities.ToolResponse`,
`type ToolError = agentcapabilities.ToolError`,
`ErrCodeStrictResolution`,
`agentcapabilities.ErrCodeStrictResolution`,
`ErrCodeRoutingMismatch`,
`agentcapabilities.ErrCodeRoutingMismatch`,
`return agentcapabilities.NewToolTextResult(text)`,
`return agentcapabilities.NewToolJSONResultWithIsError(data, isError)`,
`return agentcapabilities.NewToolResponseResult(resp)`,
} {
if !strings.Contains(assistantSrc, fragment) {
t.Errorf("Assistant tool protocol must wrap the shared tool result envelope; missing %s", fragment)
}
}
for _, fragment := range []string{
`type Request = agentcapabilities.JSONRPCRequest`,
`type Response = agentcapabilities.JSONRPCResponse`,
`type Error = agentcapabilities.JSONRPCError`,
`type InitializeResult = agentcapabilities.MCPInitializeResult`,
`type ServerInfo = agentcapabilities.MCPServerInfo`,
`type ListToolsResult = agentcapabilities.MCPListToolsResult`,
`type Resource = agentcapabilities.MCPResource`,
`type Prompt = agentcapabilities.MCPPrompt`,
} {
if strings.Contains(assistantSrc, fragment) {
t.Errorf("native Assistant tool protocol must not expose MCP/JSON-RPC wire aliases; found %s", fragment)
}
}
providerSource, err := os.ReadFile(filepath.Join("..", "ai", "providers", "provider.go"))
if err != nil {
t.Fatalf("read internal/ai/providers/provider.go: %v", err)
}
providerSrc := string(providerSource)
for _, fragment := range []string{
`type Tool = agentcapabilities.ProviderTool`,
`type ToolCall = agentcapabilities.ProviderToolCall`,
`type ToolResult = agentcapabilities.ProviderToolResult`,
`return agentcapabilities.EmptyProviderTool()`,
`return agentcapabilities.EmptyProviderToolCall()`,
} {
if !strings.Contains(providerSrc, fragment) {
t.Errorf("Assistant providers must alias the shared provider tool shape; missing %s", fragment)
}
}
for _, path := range []string{
filepath.Join("..", "ai", "chat", "tools.go"),
filepath.Join("..", "ai", "tools", "schema_projection.go"),
} {
if _, err := os.Stat(path); err == nil {
t.Errorf("%s must not keep provider projection compatibility wrappers; use internal/agentcapabilities directly", path)
} else if !os.IsNotExist(err) {
t.Fatalf("stat %s: %v", path, err)
}
}
assistantQuestionToolSource, err := os.ReadFile(filepath.Join("..", "ai", "chat", "agentic_question_tool.go"))
if err != nil {
t.Fatalf("read internal/ai/chat/agentic_question_tool.go: %v", err)
}
assistantQuestionToolSrc := string(assistantQuestionToolSource)
for _, fragment := range []string{
`const pulseQuestionToolName = agentcapabilities.PulseQuestionToolName`,
`agentcapabilities.ParsePulseQuestionToolInput(input)`,
} {
if !strings.Contains(assistantQuestionToolSrc, fragment) {
t.Errorf("Assistant question tool must use shared provider declaration; missing %s", fragment)
}
}
for _, fragment := range []string{
`InputSchema: map[string]interface{}{`,
`Name: pulseQuestionToolName`,
`Description: "Ask the user for missing information using a structured prompt.`,
`func userQuestionTool()`,
`type questionToolInputPayload struct`,
`type questionToolInputQuestion struct`,
`type questionToolInputOption struct`,
`func parseQuestionToolOptions(`,
`type questionToolType string`,
`questionToolTypeText`,
`questionToolTypeSelect`,
`func normalizeQuestionToolType(`,
`agentcapabilities.NormalizePulseQuestionToolType(parsedQuestion.Type, len(opts) > 0)`,
} {
if strings.Contains(assistantQuestionToolSrc, fragment) {
t.Errorf("Assistant question tool must not keep a chat-local provider declaration; found %s", fragment)
}
}
assistantFSMSource, err := os.ReadFile(filepath.Join("..", "ai", "chat", "fsm.go"))
if err != nil {
t.Fatalf("read internal/ai/chat/fsm.go: %v", err)
}
assistantFSMSrc := string(assistantFSMSource)
for _, fragment := range []string{
`type ToolKind = agentcapabilities.ToolCallKind`,
`ToolKindResolve = agentcapabilities.ToolCallKindResolve`,
`ToolKindRead = agentcapabilities.ToolCallKindRead`,
`ToolKindWrite = agentcapabilities.ToolCallKindWrite`,
`ToolKindUserInput = agentcapabilities.ToolCallKindUserInput`,
`return agentcapabilities.ClassifyToolCall(toolName, args)`,
} {
if !strings.Contains(assistantFSMSrc, fragment) {
t.Errorf("Assistant FSM must consume shared tool-call safety classification; missing %s", fragment)
}
}
for _, fragment := range []string{
`case agentcapabilities.PulseQuestionToolName:`,
`case "pulse_question":`,
`case "pulse_control":`,
`case "pulse_read":`,
`writeActions := map[string]bool`,
`readActions := map[string]bool`,
`actionLower := strings.ToLower(action)`,
} {
if strings.Contains(assistantFSMSrc, fragment) {
t.Errorf("Assistant FSM must not keep local tool-call safety classification; found %s", fragment)
}
}
chatTypesSource, err := os.ReadFile(filepath.Join("..", "ai", "chat", "types.go"))
if err != nil {
t.Fatalf("read internal/ai/chat/types.go: %v", err)
}
chatTypesSrc := string(chatTypesSource)
for _, fragment := range []string{
`func ToolCallFromProvider(tc agentcapabilities.ProviderToolCall) ToolCall`,
`func (t ToolCall) ProviderToolCall() agentcapabilities.ProviderToolCall`,
`type ToolResult = agentcapabilities.ProviderToolResult`,
} {
if !strings.Contains(chatTypesSrc, fragment) {
t.Errorf("Assistant chat transcript must project through shared provider tool shapes; missing %s", fragment)
}
}
aiServiceSource, err := os.ReadFile(filepath.Join("..", "ai", "service.go"))
if err != nil {
t.Fatalf("read internal/ai/service.go: %v", err)
}
aiServiceSrc := string(aiServiceSource)
for _, fragment := range []string{
`type ChatToolCall = agentcapabilities.ProviderToolCall`,
`return agentcapabilities.EmptyProviderToolCall()`,
`type ChatToolResult = agentcapabilities.ProviderToolResult`,
`return agentcapabilities.ApprovalRequiredToolMarker(`,
`return agentcapabilities.PolicyBlockedToolMarker(command, reason)`,
`agentcapabilities.ParseApprovalRequiredToolMarkerData(result)`,
`agentcapabilities.HasApprovalRequiredToolMarker(result)`,
`agentcapabilities.LegacyAssistantUtilityProviderTools()`,
`agentcapabilities.LegacyAssistantRunCommandToolName`,
`agentcapabilities.LegacyAssistantCommandArgumentName`,
`agentcapabilities.LegacyAssistantURLArgumentName`,
`agentcapabilities.LegacyAssistantResourceTypeArgumentName`,
`agentcapabilities.LegacyAssistantResourceIDArgumentName`,
`projection := newLegacyServiceProviderToolResultContextProjection(tc.ID, result, !execution.Success)`,
`projection := newLegacyServiceProviderToolResultContextProjection(tc.ID, result, true)`,
`agentcapabilities.NewProviderToolResultContextProjection(toolUseID, content, isError, legacyServiceProviderToolResultContextOptions())`,
`ToolResult: &projection.Model`,
} {
if !strings.Contains(aiServiceSrc, fragment) {
t.Errorf("AI service chat messages and tool markers must use shared provider/tool shapes; missing %s", fragment)
}
}
for _, fragment := range []string{
`ToolResult: &providers.ToolResult{`,
`resultForContext`,
`maxResultSize`,
`output truncated (`,
} {
if strings.Contains(aiServiceSrc, fragment) {
t.Errorf("AI service must not preserve local provider-result context shaping; found %s", fragment)
}
}
for _, fragment := range []string{
`Name: "run_command"`,
`Name: "fetch_url"`,
`Name: "set_resource_url"`,
`case "run_command":`,
`case "fetch_url":`,
`case "set_resource_url":`,
`tc.Input["command"]`,
`tc.Input["run_on_host"]`,
`tc.Input["target_host"]`,
`tc.Input["url"]`,
`tc.Input["resource_type"]`,
`tc.Input["resource_id"]`,
} {
if strings.Contains(aiServiceSrc, fragment) {
t.Errorf("legacy Assistant utility tool schemas and input keys must stay in agentcapabilities; AI service found %s", fragment)
}
}
if strings.Contains(aiServiceSrc, `func parseApprovalNeededMarker(`) {
t.Error("AI service approval marker handling must call the shared marker parser directly")
}
assistantChatAgentic, err := os.ReadFile(filepath.Join("..", "ai", "chat", "agentic.go"))
if err != nil {
t.Fatalf("read internal/ai/chat/agentic.go: %v", err)
}
agenticSrc := string(assistantChatAgentic)
for _, fragment := range []string{
`providerTools := executor.AssistantProviderTools(agentcapabilities.AssistantProviderToolOptions{`,
`a.tools = a.executor.AssistantProviderTools(agentcapabilities.AssistantProviderToolOptions{`,
`IncludeQuestionTool: true`,
`agentcapabilities.HasApprovalRequiredToolMarker(resultText)`,
`agentcapabilities.ParseApprovalRequiredToolMarkerData(resultText)`,
`inputWithApproval = agentcapabilities.WithApprovalArgument(inputWithApproval, approvalData.ApprovalID)`,
`agentcapabilities.NewProviderToolResultFromToolResult(tc.ID, result)`,
`projection := newProviderToolResultContextProjection(tc.ID, resultText, isError)`,
`ToolResult: &projection.Transcript`,
`ToolResult: &projection.Model`,
`toolCalls = agentcapabilities.NormalizeProviderToolCallsForExecution(data.ToolCalls)`,
`agentcapabilities.ToolInputContainsCurrentResourceReference(tc.Input)`,
} {
if !strings.Contains(agenticSrc, fragment) {
t.Errorf("Assistant agentic loop must use shared provider-result and marker vocabulary; missing %s", fragment)
}
}
if strings.Contains(agenticSrc, `ProjectAssistantProviderTools(`) || strings.Contains(agenticSrc, `ListToolGovernance()`) {
t.Error("Assistant agentic loop must request provider-tool projection from the shared executor entrypoint")
}
if strings.Contains(agenticSrc, `resultText = FormatToolResult(result)`) {
t.Error("Assistant agentic loop must project MCP tool results through shared provider-result construction, not local result text assignment")
}
for _, fragment := range []string{
`agentcapabilities.ApprovalRequiredToolMarkerPayloadJSON(resultText)`,
`var approvalData struct`,
"\n\t\t\t\t\t\t\tagentcapabilities.WithApprovalArgument(inputWithApproval, approvalData.ApprovalID)",
`ToolResult: &providers.ToolResult{`,
`ToolResult: &ToolResult{`,
`modelResultText := truncateToolResultForModel(resultText)`,
`func toolInputContainsCurrentResourceReference`,
`append(providerTools, userQuestionTool())`,
`userQuestionTool()`,
`toolCalls = NormalizeProviderToolCallsForExecution(data.ToolCalls)`,
`func ConvertPulseToolsToProvider`,
`func ConvertPulseToolsToProviderWithGovernance`,
`func ConvertProviderToolCallToMCP`,
`func NormalizeProviderToolCallForExecution`,
`func NormalizeProviderToolCallsForExecution`,
} {
if strings.Contains(agenticSrc, fragment) {
t.Errorf("Assistant agentic loop must not assemble provider tool-result structs locally; found %s", fragment)
}
}
currentResourceSource, err := os.ReadFile(filepath.Join("..", "ai", "tools", "current_resource.go"))
if err != nil {
t.Fatalf("read internal/ai/tools/current_resource.go: %v", err)
}
currentResourceSrc := string(currentResourceSource)
for _, fragment := range []string{
`const currentResourceHandle = agentcapabilities.CurrentResourceHandle`,
`return agentcapabilities.IsCurrentResourceReference(value)`,
} {
if !strings.Contains(currentResourceSrc, fragment) {
t.Errorf("Assistant current_resource executor must use shared handle semantics; missing %s", fragment)
}
}
for _, fragment := range []string{
`"attached_resource"`,
`"selected_resource"`,
`"this_resource"`,
`"redacted by policy"`,
} {
if strings.Contains(currentResourceSrc, fragment) {
t.Errorf("Assistant current_resource executor must not keep local handle aliases; found %s", fragment)
}
}
assistantChatContext, err := os.ReadFile(filepath.Join("..", "ai", "chat", "agentic_context.go"))
if err != nil {
t.Fatalf("read internal/ai/chat/agentic_context.go: %v", err)
}
agenticContextSrc := string(assistantChatContext)
for _, fragment := range []string{
`agentcapabilities.ProviderToolResultModelContent(text, providerToolResultContextOptions())`,
`agentcapabilities.NewProviderToolResultContextProjection(toolUseID, content, isError, providerToolResultContextOptions())`,
`pm.ToolResult = &projection.Model`,
`agentcapabilities.NewProviderToolErrorResult(`,
} {
if !strings.Contains(agenticContextSrc, fragment) {
t.Errorf("Assistant provider message conversion must use shared provider-result construction; missing %s", fragment)
}
}
for _, fragment := range []string{
`func newProviderToolResult(`,
`func newProviderToolErrorResult(`,
`ToolResult: &providers.ToolResult{`,
} {
if strings.Contains(agenticContextSrc, fragment) {
t.Errorf("Assistant provider message conversion must not preserve local provider-result wrappers or structs; found %s", fragment)
}
}
assistantChatService, err := os.ReadFile(filepath.Join("..", "ai", "chat", "service.go"))
if err != nil {
t.Fatalf("read internal/ai/chat/service.go: %v", err)
}
chatServiceSrc := string(assistantChatService)
for _, fragment := range []string{
`manifest = s.executor.ListToolGovernance()`,
`agentcapabilities.ProviderToolNames(offeredTools)`,
`agentcapabilities.ProviderToolGovernanceDescriptors(offeredTools)`,
`agentcapabilities.BuildAssistantToolGovernancePromptSection(manifest, orderedOfferedNames)`,
`tools.CanonicalToolGovernanceForManifestSurface(`,
`agentcapabilities.SurfaceIDPulseAssistant`,
`fallbackAssistantToolGovernanceOfferedNames(manifest)`,
`agentcapabilities.ManifestSurfaceAffordances(`,
`providerTools := s.executor.AssistantProviderTools(agentcapabilities.AssistantProviderToolOptions{`,
`IncludeQuestionTool: includeQuestionTool`,
`agentcapabilities.BuildPulseAssistantOperatingInstructions()`,
`func (s *Service) executeAssistantRegistryToolDirect(ctx context.Context, toolName string, args map[string]interface{}, opts agentcapabilities.DirectToolExecutionOptions) (agentcapabilities.DirectToolExecutionOutcome, error)`,
`outcome, executionErr := s.executeAssistantRegistryToolDirect(ctx, agentcapabilities.PulseRunCommandToolName, args, agentcapabilities.DirectToolExecutionOptions{`,
`outcome, executionErr := s.executeAssistantRegistryToolDirect(ctx, toolName, args, agentcapabilities.DirectToolExecutionOptions{`,
`result, toolErr := executor.ExecuteTool(ctx, toolName, args)`,
`agentcapabilities.InterpretDirectToolExecution(result`,
`FailurePrefix: "command execution failed"`,
`ApprovalRequiredMessage: "command requires approval (unexpected in autonomous mode)"`,
`PolicyBlockedMessage: "command blocked by security policy"`,
`FailurePrefix: "tool execution failed"`,
`ApprovalRequiredMessage: "tool requires approval (unexpected in fix execution)"`,
`PolicyBlockedMessage: "tool blocked by security policy"`,
} {
if !strings.Contains(chatServiceSrc, fragment) {
t.Errorf("Assistant chat service must use shared direct result execution mapping; missing %s", fragment)
}
}
for _, fragment := range []string{
`agentcapabilities.DirectMCPToolExecutionOptions`,
`agentcapabilities.DirectMCPToolExecutionOutcome`,
`agentcapabilities.InterpretDirectMCPToolExecution`,
} {
if strings.Contains(chatServiceSrc, fragment) {
t.Errorf("Assistant chat service must use neutral direct tool execution helpers, found %s", fragment)
}
}
if strings.Contains(chatServiceSrc, `ProjectAssistantProviderTools(`) {
t.Error("Assistant chat service must request provider-tool projection from the shared executor entrypoint")
}
if strings.Contains(chatServiceSrc, `tools.CanonicalToolGovernanceForSurface(`) ||
strings.Contains(chatServiceSrc, `tools.ToolGovernanceSurfacePulseAssistant`) {
t.Error("Assistant chat service fallback governance must enter through manifest surface affordances")
}
for _, fragment := range []string{
`b.WriteString("## AVAILABLE TOOL GOVERNANCE`,
`agentcapabilities.ToolGovernancePromptOptions{`,
`agentcapabilities.PulseQuestionToolGovernancePromptLine()`,
`assistantGovernanceOfferedToolNames`,
`userQuestionTool()`,
`mode := tool.ActionMode`,
`approvalSummary := strings.TrimSpace(tool.ApprovalSummary)`,
`fmt.Sprintf("- %s: mode=%s; approval=%s`,
`for _, content := range result.Content`,
`resultText += content.Text`,
`agentcapabilities.HasApprovalRequiredToolMarker(resultText)`,
`agentcapabilities.HasPolicyBlockedToolMarker(resultText)`,
`interpreted := agentcapabilities.InterpretToolResult(result)`,
`if interpreted.IsError`,
`if interpreted.ApprovalRequired`,
`if interpreted.PolicyBlocked`,
`func assistantQuestionToolGovernancePromptLine() string`,
} {
if strings.Contains(chatServiceSrc, fragment) {
t.Errorf("Assistant chat service must not flatten or interpret direct MCP result outcomes locally; found %s", fragment)
}
}
chatServiceAdapterSource, err := os.ReadFile(filepath.Join("..", "api", "chat_service_adapter.go"))
if err != nil {
t.Fatalf("read internal/api/chat_service_adapter.go: %v", err)
}
chatServiceAdapterSrc := string(chatServiceAdapterSource)
for _, fragment := range []string{
`func adaptChatMessage(m chat.Message) ai.ChatMessage`,
`msg.ToolCalls = append(msg.ToolCalls, tc.ProviderToolCall())`,
`toolResult := *m.ToolResult`,
} {
if !strings.Contains(chatServiceAdapterSrc, fragment) {
t.Errorf("chat service adapter must bridge messages through shared provider tool shapes; missing %s", fragment)
}
}
orchestratorDepsSource, err := os.ReadFile(filepath.Join("..", "..", "pkg", "aicontracts", "orchestrator_deps.go"))
if err != nil {
t.Fatalf("read pkg/aicontracts/orchestrator_deps.go: %v", err)
}
orchestratorDepsSrc := string(orchestratorDepsSource)
for _, fragment := range []string{
`func OrchestratorToolCallInfoFromProvider(tc agentcapabilities.ProviderToolCall) OrchestratorToolCallInfo`,
`func (t OrchestratorToolCallInfo) ProviderToolCall() agentcapabilities.ProviderToolCall`,
`func OrchestratorToolResultInfoFromProvider(result agentcapabilities.ProviderToolResult) OrchestratorToolResultInfo`,
`func (r OrchestratorToolResultInfo) ProviderToolResult() agentcapabilities.ProviderToolResult`,
`return agentcapabilities.NewProviderToolResult(r.ToolUseID, r.Content, r.IsError)`,
} {
if !strings.Contains(orchestratorDepsSrc, fragment) {
t.Errorf("orchestrator dependency contract must bridge messages through shared provider tool shapes; missing %s", fragment)
}
}
aiHandlersSource, err := os.ReadFile(filepath.Join("..", "api", "ai_handlers.go"))
if err != nil {
t.Fatalf("read internal/api/ai_handlers.go: %v", err)
}
aiHandlersSrc := string(aiHandlersSource)
for _, fragment := range []string{
`aicontracts.OrchestratorToolCallInfoFromProvider(tc.ProviderToolCall())`,
`toolResult := aicontracts.OrchestratorToolResultInfoFromProvider(*msg.ToolResult)`,
} {
if !strings.Contains(aiHandlersSrc, fragment) {
t.Errorf("orchestrator chat adapter must bridge messages through shared provider tool shapes; missing %s", fragment)
}
}
patrolRuntime, err := os.ReadFile(filepath.Join("..", "ai", "patrol_ai.go"))
if err != nil {
t.Fatalf("read internal/ai/patrol_ai.go: %v", err)
}
patrolSrc := string(patrolRuntime)
if strings.Contains(patrolSrc, "func formatToolResult(") {
t.Error("Patrol runtime must not keep a local result-text wrapper; call agentcapabilities.ToolResultText at the execution site")
}
patrolFindings, err := os.ReadFile(filepath.Join("..", "ai", "patrol_findings.go"))
if err != nil {
t.Fatalf("read internal/ai/patrol_findings.go: %v", err)
}
patrolFindingsSrc := string(patrolFindings)
for _, fragment := range []string{
`agentcapabilities.InterpretToolResult(result)`,
`if interpreted.IsError`,
`if interpreted.ApprovalRequired`,
`if interpreted.PolicyBlocked`,
} {
if !strings.Contains(patrolFindingsSrc, fragment) {
t.Errorf("Patrol tool-call records must use shared tool result interpretation; missing %s", fragment)
}
}
for _, fragment := range []string{
`output = agentcapabilities.MCPToolResultText(result)`,
`agentcapabilities.InterpretMCPToolResult(result)`,
`agentcapabilities.MCPToolResultInterpretation`,
`success = !result.IsError`,
`if result.IsError`,
`formatToolResult(result)`,
} {
if strings.Contains(patrolFindingsSrc, fragment) {
t.Errorf("Patrol tool-call records must not duplicate shared result interpretation locally; found %s", fragment)
}
}
assistantRegistry, err := os.ReadFile(filepath.Join("..", "ai", "tools", "registry.go"))
if err != nil {
t.Fatalf("read internal/ai/tools/registry.go: %v", err)
}
registrySrc := string(assistantRegistry)
for _, fragment := range []string{
`type ControlLevel = agentcapabilities.ControlLevel`,
`ControlLevelReadOnly ControlLevel = agentcapabilities.ControlLevelReadOnly`,
`ControlLevelControlled ControlLevel = agentcapabilities.ControlLevelControlled`,
`ControlLevelAutonomous ControlLevel = agentcapabilities.ControlLevelAutonomous`,
`!agentcapabilities.ControlLevelAllowsControlTools(controlLevel)`,
`!agentcapabilities.ControlLevelAllowsControlTools(e.controlLevel)`,
`agentcapabilities.NewToolGovernanceDescriptor(`,
`tool.Definition = tool.Definition.NormalizeCollections()`,
`result = append(result, tool.Definition.NormalizeCollections())`,
`params, invalidResult, ok := agentcapabilities.PrepareToolRegistryExecution(name, args)`,
`agentcapabilities.NewUnknownToolResult(name)`,
`agentcapabilities.NewControlToolsDisabledToolResult()`,
`return result.NormalizeCollections(), err`,
} {
if !strings.Contains(registrySrc, fragment) {
t.Errorf("Assistant tool registry must use shared agentcapabilities contracts; missing %s", fragment)
}
}
for _, fragment := range []string{
`assistantControlToolsDisabledMessage`,
`func normalizeToolGovernance(`,
`DefaultApprovalPolicyDescription(governance.ApprovalPolicy)`,
`governance.ActionMode = ToolActionWrite`,
`governance.ApprovalPolicy = ToolApprovalActionPlan`,
`invalid tools/call params:`,
`unknown tool: %s`,
`agentcapabilities.ControlToolsDisabledMessage`,
} {
if strings.Contains(registrySrc, fragment) {
t.Errorf("Assistant tool registry must not preserve local governance defaults; found %s", fragment)
}
}
assistantRegistryToolFiles := []string{
filepath.Join("..", "ai", "tools", "tools_alerts.go"),
filepath.Join("..", "ai", "tools", "tools_control.go"),
filepath.Join("..", "ai", "tools", "tools_discovery.go"),
filepath.Join("..", "ai", "tools", "tools_docker.go"),
filepath.Join("..", "ai", "tools", "tools_file.go"),
filepath.Join("..", "ai", "tools", "tools_kubernetes.go"),
filepath.Join("..", "ai", "tools", "tools_knowledge.go"),
filepath.Join("..", "ai", "tools", "tools_metrics.go"),
filepath.Join("..", "ai", "tools", "tools_patrol.go"),
filepath.Join("..", "ai", "tools", "tools_pmg.go"),
filepath.Join("..", "ai", "tools", "tools_query.go"),
filepath.Join("..", "ai", "tools", "tools_read.go"),
filepath.Join("..", "ai", "tools", "tools_storage.go"),
filepath.Join("..", "ai", "tools", "tools_summarize.go"),
}
assistantRegistryToolSrc := strings.Builder{}
for _, path := range assistantRegistryToolFiles {
source, err := os.ReadFile(path)
if err != nil {
t.Fatalf("read %s: %v", path, err)
}
assistantRegistryToolSrc.Write(source)
assistantRegistryToolSrc.WriteString("\n")
}
registryToolDeclarations := assistantRegistryToolSrc.String()
for _, fragment := range []string{
`Name: agentcapabilities.PulseAlertsToolName`,
`Name: agentcapabilities.PulseControlToolName`,
`Name: agentcapabilities.PulseDiscoveryToolName`,
`Name: agentcapabilities.PulseDockerToolName`,
`Name: agentcapabilities.PulseFileEditToolName`,
`Name: agentcapabilities.PulseKubernetesToolName`,
`Name: agentcapabilities.PulseKnowledgeToolName`,
`Name: agentcapabilities.PulseMetricsToolName`,
`Name: agentcapabilities.PatrolReportFindingToolName`,
`Name: agentcapabilities.PatrolResolveFindingToolName`,
`Name: agentcapabilities.PatrolGetFindingsToolName`,
`Name: agentcapabilities.PulsePMGToolName`,
`Name: agentcapabilities.PulseQueryToolName`,
`Name: agentcapabilities.PulseReadToolName`,
`Name: agentcapabilities.PulseStorageToolName`,
`Name: agentcapabilities.PulseSummarizeToolName`,
} {
if !strings.Contains(registryToolDeclarations, fragment) {
t.Errorf("Assistant registry tool declarations must use shared tool identities; missing %s", fragment)
}
}
for _, fragment := range []string{
`Name: "pulse_`,
`Name: "pulse_`,
`Name: "patrol_`,
`Name: "patrol_`,
} {
if strings.Contains(registryToolDeclarations, fragment) {
t.Errorf("Assistant registry tool declarations must not own local Pulse tool-name strings; found %s", fragment)
}
}
assistantPatrolTools, err := os.ReadFile(filepath.Join("..", "ai", "tools", "tools_patrol.go"))
if err != nil {
t.Fatalf("read internal/ai/tools/tools_patrol.go: %v", err)
}
assistantPatrolToolsSrc := string(assistantPatrolTools)
for _, fragment := range []string{
`agentcapabilities.FindingIDArgumentName`,
`agentcapabilities.ReasonArgumentName`,
`return NewJSONResult(result), nil`,
} {
if !strings.Contains(assistantPatrolToolsSrc, fragment) {
t.Errorf("Assistant Patrol tools must use shared finding vocabulary and JSON result envelope; missing %s", fragment)
}
}
for _, fragment := range []string{
`json.Marshal(result)`,
`NewTextResult(string(b))`,
} {
if strings.Contains(assistantPatrolToolsSrc, fragment) {
t.Errorf("Assistant Patrol tools must not manually marshal JSON text results; found %s", fragment)
}
}
assistantProjection, err := os.ReadFile(filepath.Join("..", "ai", "tools", "provider_projection.go"))
if err != nil {
t.Fatalf("read internal/ai/tools/provider_projection.go: %v", err)
}
assistantProjectionSrc := string(assistantProjection)
for _, fragment := range []string{
`func (e *PulseToolExecutor) AssistantProviderTools(opts agentcapabilities.AssistantProviderToolOptions) []agentcapabilities.ProviderTool`,
`return agentcapabilities.ProjectPulseAssistantProviderTools(agentcapabilities.CanonicalManifest(), e.ListTools(), e.ListToolGovernance(), opts)`,
`return agentcapabilities.ProjectPulseAssistantProviderTools(agentcapabilities.CanonicalManifest(), nil, nil, opts)`,
} {
if !strings.Contains(assistantProjectionSrc, fragment) {
t.Errorf("Assistant provider-tool projection must stay centralized on the shared executor; missing %s", fragment)
}
}
for _, fragment := range []string{
`return agentcapabilities.ProjectAssistantProviderTools(e.ListTools(), e.ListToolGovernance(), opts)`,
`return agentcapabilities.ProjectAssistantProviderTools(nil, nil, opts)`,
} {
if strings.Contains(assistantProjectionSrc, fragment) {
t.Errorf("Assistant provider-tool projection must enter through manifest surface affordances; found %s", fragment)
}
}
assistantToolNames, err := os.ReadFile(filepath.Join("..", "ai", "tools", "names.go"))
if err != nil {
t.Fatalf("read internal/ai/tools/names.go: %v", err)
}
assistantToolNamesSrc := string(assistantToolNames)
for _, fragment := range []string{
`agentcapabilities.ProviderToolNameCatalog`,
`func AssistantProviderToolNameCatalog() agentcapabilities.ProviderToolNameCatalog`,
`return AssistantProviderToolNameCatalog().Has(name)`,
`return AssistantProviderToolNameCatalog().HasPrefix(prefix)`,
`return knownToolNameCatalog`,
`agentcapabilities.NewAssistantProviderToolNameCatalog(e.registry.allNames())`,
} {
if !strings.Contains(assistantToolNamesSrc, fragment) {
t.Errorf("Assistant tool-name allowlist must adapt the shared provider-tool catalog; missing %s", fragment)
}
}
for _, fragment := range []string{
`agentcapabilities.AssistantNativeProviderToolNames()`,
`knownToolNamesList`,
`knownToolNamesSet`,
`strings.HasPrefix(name, prefix)`,
} {
if strings.Contains(assistantToolNamesSrc, fragment) {
t.Errorf("Assistant tool-name allowlist must not keep local provider-tool catalog logic; found %s", fragment)
}
}
if strings.Contains(assistantToolNamesSrc, `"pulse_question"`) {
t.Error("Assistant tool-name allowlist must not hard-code native Assistant provider tool names")
}
assistantSanitize, err := os.ReadFile(filepath.Join("..", "ai", "chat", "agentic_sanitize.go"))
if err != nil {
t.Fatalf("read internal/ai/chat/agentic_sanitize.go: %v", err)
}
assistantSanitizeSrc := string(assistantSanitize)
for _, fragment := range []string{
`agentcapabilities.ProviderToolCallArtifactIndex(content, tools.AssistantProviderToolNameCatalog())`,
`agentcapabilities.SplitTrailingProviderToolNamePrefix(content, tools.AssistantProviderToolNameCatalog())`,
} {
if !strings.Contains(assistantSanitizeSrc, fragment) {
t.Errorf("Assistant chat sanitizer must use shared provider tool-call artifact detection; missing %s", fragment)
}
}
for _, fragment := range []string{
`dsmlMarkers`,
`xmlToolCallRe`,
`pipeMarkerRe`,
`minimaxMarkerRe`,
`jsonToolCallRe`,
`plainFunctionToolCallRe`,
`findJSONToolCallLeak`,
`findPlainFunctionToolCallLeak`,
} {
if strings.Contains(assistantSanitizeSrc, fragment) {
t.Errorf("Assistant chat sanitizer must not preserve local provider artifact detection; found %s", fragment)
}
}
}
// TestContract_AgentProbeUsesSharedAgentCapabilityProjection pins the reference
// client boundary: the probe may demonstrate transport mechanics, but manifest
// lookup and route projection must use the same shared helpers as MCP.
func TestContract_AgentProbeUsesSharedAgentCapabilityProjection(t *testing.T) {
source, err := os.ReadFile(filepath.Join("..", "..", "cmd", "agent-probe", "main.go"))
if err != nil {
t.Fatalf("read cmd/agent-probe/main.go: %v", err)
}
src := string(source)
required := []string{
`internal/agentcapabilities`,
`agentcapabilities.FleetContextCapabilityName`,
`agentcapabilities.ResourceContextCapabilityName`,
`agentcapabilities.FindCapability(manifest.Capabilities, agentcapabilities.EventSubscriptionCapabilityName)`,
`agentcapabilities.FetchManifest(context.Background(), client, *baseURL)`,
`agentcapabilities.CallRequestResponseCapabilityHTTPBodyByName(`,
`agentcapabilities.StreamAgentActionableSSERecords(ctx, nil, baseURL, path, token, func(record agentcapabilities.SSERecord) bool`,
}
for _, fragment := range required {
if !strings.Contains(src, fragment) {
t.Errorf("agent-probe must use the shared agent capability projection helpers; missing %s", fragment)
}
}
forbidden := []string{
`agentcapabilities.FindCapability(manifest.Capabilities, "get_fleet_context")`,
`agentcapabilities.FindCapability(manifest.Capabilities, "get_resource_context")`,
`agentcapabilities.CallCapabilityHTTPByName(`,
`agentcapabilities.CallCapabilityHTTP(ctx, client, baseURL, token, cap, args)`,
`resp.FailureError()`,
`strings.Replace(contextCap.Path`,
`byName := map[string]agentCapability`,
`type errorEnvelope struct`,
`baseURL + "/api/agent/capabilities"`,
`req.Header.Set("X-API-Token", token)`,
`agentcapabilities.DecodeErrorEnvelope(body)`,
`req.Header.Set("Accept", "text/event-stream")`,
`http.DefaultClient.Do(req)`,
`scanner := bufio.NewScanner(resp.Body)`,
`strings.HasPrefix(line, "event: ")`,
`strings.HasPrefix(line, "data: ")`,
`agentcapabilities.StreamAgentSSERecords(ctx, nil, baseURL, path, token`,
`agentcapabilities.IsTransportEventKind(record.Event)`,
}
for _, fragment := range forbidden {
if strings.Contains(src, fragment) {
t.Errorf("agent-probe must not preserve local manifest projection logic; found %s", fragment)
}
}
}
// TestContract_AgentSurfaceErrorEnvelopeUsesSharedAgentCapabilitiesType pins
// the stable agent error envelope as part of the shared agent capabilities
// contract instead of an API-local map or probe-local struct.
func TestContract_AgentSurfaceErrorEnvelopeUsesSharedAgentCapabilitiesType(t *testing.T) {
source, err := os.ReadFile("middleware_tenant.go")
if err != nil {
t.Fatalf("read middleware_tenant.go: %v", err)
}
if !strings.Contains(string(source), "agentcapabilities.NewErrorEnvelope(code, message, details)") {
t.Error("writeJSONErrorWithDetails must emit the shared agentcapabilities.ErrorEnvelope shape")
}
agentCapabilitiesSource, err := os.ReadFile(filepath.Join("..", "agentcapabilities", "errors.go"))
if err != nil {
t.Fatalf("read internal/agentcapabilities/errors.go: %v", err)
}
for _, fragment := range []string{
"type ErrorEnvelope struct",
"Error string",
"Message string",
"Details map[string]string",
`AgentErrCodeResourceNotFound = "resource_not_found"`,
`AgentErrCodeOperatorStateNotSet = "operator_state_not_set"`,
`AgentErrCodeInvalidActionRequest = "invalid_action_request"`,
`AgentErrCodeActionExecutionUnavailable = "action_execution_unavailable"`,
"func NewErrorEnvelope(",
"func DecodeErrorEnvelope(",
} {
if !strings.Contains(string(agentCapabilitiesSource), fragment) {
t.Errorf("agentcapabilities must own the shared agent error envelope; missing %s", fragment)
}
}
}
// TestContract_AgentCapabilitiesManifestDeclaresProvisioningSurface pins the
// node-lifecycle onboarding surface in the public agent manifest. This is the
// backend API payload proof required when the hand-authored manifest starts
// projecting /api/config/nodes and /api/discover to external agents.
func TestContract_AgentCapabilitiesManifestDeclaresProvisioningSurface(t *testing.T) {
src := readAgentCapabilitiesManifestSource(t)
required := []string{
`"provisioning"`,
`ActionMode:`,
`ApprovalPolicy:`,
`StrictObjectInputSchema`,
`agentCapabilityActionModeRead`,
`agentCapabilityActionModeMixed`,
`agentCapabilityActionModeWrite`,
`agentCapabilityApprovalPolicyScopeOnly`,
`agentCapabilityApprovalPolicyActionPlan`,
`ListNodesCapabilityName`,
`Path: "/api/config/nodes"`,
`Scope: agentCapabilityScopeSettingsRead`,
`AddNodeCapabilityName`,
`InputSchema: addNodeInputSchema()`,
`UpdateNodeCapabilityName`,
`InputSchema: updateNodeInputSchema()`,
`RemoveNodeCapabilityName`,
`InputSchema: nodeIDInputSchema()`,
`TestNodeCredentialsCapabilityName`,
`Path: "/api/config/nodes/test-config"`,
`TestNodeConnectionCapabilityName`,
`Path: "/api/config/nodes/{nodeId}/test"`,
`RefreshNodeClusterMembershipCapabilityName`,
`Path: "/api/config/nodes/{nodeId}/refresh-cluster"`,
`DiscoverLANCapabilityName`,
`Path: "/api/discover"`,
`InputSchema: discoverLANInputSchema()`,
`"tokenValue"`,
`StrictObjectInputSchema`,
}
for _, fragment := range required {
if !strings.Contains(src, fragment) {
t.Errorf("agent capabilities manifest must declare provisioning contract fragment %s", fragment)
}
}
manifest := agentcapabilities.CanonicalManifest()
mcp, ok := agentcapabilities.FindManifestSurfaceToolContract(manifest, agentcapabilities.SurfaceIDPulseMCP)
if !ok {
t.Fatal("agent capabilities manifest must publish Pulse MCP surface tool contract")
}
for _, name := range []string{
agentcapabilities.ListNodesCapabilityName,
agentcapabilities.AddNodeCapabilityName,
agentcapabilities.UpdateNodeCapabilityName,
agentcapabilities.RemoveNodeCapabilityName,
agentcapabilities.TestNodeCredentialsCapabilityName,
agentcapabilities.TestNodeConnectionCapabilityName,
agentcapabilities.RefreshNodeClusterMembershipCapabilityName,
agentcapabilities.DiscoverLANCapabilityName,
} {
if _, ok := agentcapabilities.FindCapability(manifest.Capabilities, name); !ok {
t.Errorf("agent capabilities manifest must declare provisioning capability %q", name)
}
if !stringSliceContains(mcp.ToolNames, name) {
t.Errorf("Pulse MCP surface contract must explicitly allow provisioning capability %q", name)
}
}
}
// TestContract_AgentCapabilitiesManifestDeclaresTypedActionAndFindingSchemas
// pins that high-risk external-agent tools expose exact manifest-owned
// argument schemas instead of adapter-local free-form body conventions.
func TestContract_AgentCapabilitiesManifestDeclaresTypedActionAndFindingSchemas(t *testing.T) {
src := readAgentCapabilitiesManifestSource(t)
required := []string{
`operatorStateInputSchema()`,
`findingIDInputSchema(`,
`snoozeFindingInputSchema()`,
`dismissFindingInputSchema()`,
`actionPlanInputSchema()`,
`actionDecisionInputSchema()`,
`actionExecutionInputSchema()`,
`FindingIDArgumentName: stringOption(description)`,
`FindingIDArgumentName, "duration_hours"`,
`FindingIDArgumentName, ReasonArgumentName`,
`NoteArgumentName: stringOption("Optional operator note explaining the dismissal.")`,
`ResourceIDArgumentName, "intentionallyOffline", "neverAutoRemediate"`,
`"maintenanceStartAt": {"maintenanceEndAt"}`,
`"not_an_issue", "expected_behavior", "will_fix_later"`,
`"approved", "rejected"`,
`RequestIDArgumentName, ResourceIDArgumentName, CapabilityNameArgumentName, ReasonArgumentName, RequestedByArgumentName`,
`ActionIDArgumentName, OutcomeArgumentName`,
}
for _, fragment := range required {
if !strings.Contains(src, fragment) {
t.Errorf("agent capabilities manifest must declare typed action/finding input schema fragment %s", fragment)
}
}
}
// TestContract_AgentCapabilitiesManifestDeclaresStructuredOutputSchemas pins
// that MCP structuredContent validation metadata is owned by the canonical
// capabilities manifest rather than a local adapter table.
func TestContract_AgentCapabilitiesManifestDeclaresStructuredOutputSchemas(t *testing.T) {
src := readAgentCapabilitiesManifestSource(t)
required := []string{
`fleetContextOutputSchema()`,
`operationsLoopStatusOutputSchema()`,
`resourceContextOutputSchema()`,
`operatorStateOutputSchema()`,
`toolArrayOutputSchema("Configured infrastructure source records visible to the agent.")`,
`nodeConnectionTestOutputSchema()`,
`clusterRefreshOutputSchema()`,
`discoveryOutputSchema()`,
`findingActionOutputSchema()`,
`actionPlanOutputSchema()`,
`actionDecisionOutputSchema()`,
`actionExecutionOutputSchema()`,
`OutputSchema: fleetContextOutputSchema()`,
`OutputSchema: operationsLoopStatusOutputSchema()`,
`OutputSchema: resourceContextOutputSchema()`,
`OutputSchema: operatorStateOutputSchema()`,
`OutputSchema: toolArrayOutputSchema("Patrol finding records returned by the lifecycle endpoint.")`,
`OutputSchema: actionPlanOutputSchema()`,
`"resources", "generatedAt"`,
`"nextAction", "progressLabel", "steps", "patrolEvidenceCount", "patrolIssueEvidenceCount", "activeFindingCount", "pendingApprovalCount", "governedActionCount", "approvedDecisionCount", "rejectedDecisionCount", "verifiedOutcomeCount", "operationsLoopStarterCount", "assistantOperationsLoopStarterCount", "patrolOperationsLoopStarterCount", "patrolControlOperationsLoopStarterCount", "patrolControlCompletedOperationsLoopCount", "patrolControlResolvedOperationsLoopCount", "patrolControlValueState", "patrolAutonomyOperationsLoopStarterCount", "patrolAutonomyCompletedOperationsLoopCount", "patrolAutonomyResolvedOperationsLoopCount", "patrolAutonomyValueState", "proActivationOperationsLoopStarterCount", "proActivationCompletedOperationsLoopCount", "proActivationResolvedOperationsLoopCount", "proActivationValueProofState", "mcpOperationsLoopStarterCount", "externalAgentReady", "windowStart", "generatedAt"`,
`"Count of recent decision-backed governed actions without action ids or command content."`,
`"Count of recent approved governed-action decisions without action ids or command content."`,
`"Count of recent rejected governed-action decisions without action ids or command content."`,
`"Count of recent approved governed actions with a verified post-action outcome."`,
`"Count of content-free Patrol work prompt starter events in the evidence window."`,
`"Count of Patrol work prompt starters rendered by Pulse Assistant in the evidence window."`,
`"Count of Patrol work prompt starters launched from Pulse Patrol in the evidence window."`,
`"Count of Patrol work prompt starters launched from the first-party Patrol control journey in the evidence window."`,
`"Count-only evidence that the Patrol control journey reached a terminal Patrol work result in the evidence window: Patrol issue evidence, contextual Assistant or external-agent collaboration, and either a rejected governed decision or an approved governed decision with a verified outcome."`,
`"Count-only evidence that the Patrol control journey reached a verified Patrol work result in the evidence window: Patrol issue evidence, contextual Assistant or external-agent collaboration, approved governed decision, and verified outcome."`,
`"Content-safe Patrol control value state. Only verified means Patrol completed a verified work outcome; governed_decision_recorded means a safe terminal decision exists without verified outcome evidence."`,
`"Compatibility field retained for older external-agent clients; primary clients should use patrolControlOperationsLoopStarterCount."`,
`"Compatibility alias for patrolControlCompletedOperationsLoopCount."`,
`"Compatibility alias for patrolControlResolvedOperationsLoopCount."`,
`"Compatibility alias for patrolControlValueState."`,
`"Count of Patrol work prompt starters rendered by the Pulse MCP surface in the evidence window."`,
`"canonicalId", "resourceType", "resourceName", "activeFindings", "pendingApprovals", "recentActions", "contextSections", "generatedAt"`,
`"canonicalId", "intentionallyOffline", "neverAutoRemediate", "setAt"`,
`ActionIDArgumentName, RequestIDArgumentName, "allowed", "requiresApproval", "approvalPolicy", "rollbackAvailable", "plannedAt", "expiresAt", "resourceVersion", "policyVersion", "planHash"`,
}
for _, fragment := range required {
if !strings.Contains(src, fragment) {
t.Errorf("agent capabilities manifest must declare structured output schema fragment %s", fragment)
}
}
}
// TestContract_AgentResourceContextEndpointSurfacesStableShape pins
// the agent-paradigm substrate contract: the bundled endpoint must
// return arrays (never null) for activeFindings and recentActions so
// agents can iterate without nil-checking, must omit operatorState
// when no entry exists so agents can branch on field presence, and
// must preserve refusal-token prefixes verbatim so agents can branch
// on the stable code without parsing human messages. Without this
// pin, future refactors of the projection helpers could silently
// drift the wire shape and break external agent consumers.
func TestContract_AgentResourceContextEndpointSurfacesStableShape(t *testing.T) {
source, err := os.ReadFile("agent_resource_context.go")
if err != nil {
t.Fatalf("read agent_resource_context.go: %v", err)
}
src := string(source)
// Always-array initialization for the iteration-safe contract.
if !regexp.MustCompile(`ActiveFindings:\s+\[\]AgentResourceFindingSnapshot\{\},`).MatchString(src) {
t.Error("activeFindings must default to an empty slice, not nil — agents iterate without nil-checks")
}
if !regexp.MustCompile(`RecentActions:\s+\[\]AgentResourceActionSummary\{\},`).MatchString(src) {
t.Error("recentActions must default to an empty slice, not nil")
}
// Server-computed maintenance-active flag — server pre-computes so
// agents don't re-evaluate timestamps client-side.
if !strings.Contains(src, "MaintenanceWindowActive: state.IsInMaintenanceAt(now)") {
t.Error("MaintenanceWindowActive must be computed server-side via state.IsInMaintenanceAt(now)")
}
// Refusal-token preservation: ErrorMessage flows through verbatim
// from the audit record's ExecutionResult, no rewriting.
if !strings.Contains(src, "summary.ErrorMessage = audit.Result.ErrorMessage") {
t.Error("audit ErrorMessage must round-trip verbatim so refusal tokens (resource_remediation_locked:, plan_drift:) reach agents")
}
// pendingApprovals must initialize as an empty slice so agents
// iterate without nil-checking — same iteration-safe contract
// as activeFindings and recentActions.
if !regexp.MustCompile(`PendingApprovals:\s+\[\]AgentResourceApprovalSummary\{\},`).MatchString(src) {
t.Error("pendingApprovals must default to an empty slice, not nil — agents iterate without nil-checks")
}
if !regexp.MustCompile(`ContextSections:\s+\[\]AgentResourceContextSection\{\},`).MatchString(src) {
t.Error("contextSections must default to an empty slice, not nil — resource-aware agents iterate without nil-checks")
}
contextType := reflect.TypeOf(AgentResourceContext{})
operatorStateField, ok := contextType.FieldByName("OperatorState")
if !ok {
t.Fatal("AgentResourceContext must carry OperatorState")
}
if operatorStateField.Type != reflect.TypeOf((*AgentResourceOperatorState)(nil)) ||
operatorStateField.Tag.Get("json") != "operatorState,omitempty" {
t.Error("operatorState must be an omitempty pointer so absent entries surface as a missing field")
}
discoveryReadinessField, ok := contextType.FieldByName("DiscoveryReadiness")
if !ok {
t.Fatal("AgentResourceContext must carry DiscoveryReadiness")
}
if discoveryReadinessField.Type != reflect.TypeOf((*AgentResourceDiscoveryReadiness)(nil)) ||
discoveryReadinessField.Tag.Get("json") != "discoveryReadiness,omitempty" {
t.Error("discoveryReadiness must be an omitempty pointer to the shared resource readiness projection")
}
pendingApprovalsField, ok := contextType.FieldByName("PendingApprovals")
if !ok {
t.Fatal("AgentResourceContext must carry PendingApprovals")
}
if pendingApprovalsField.Type != reflect.TypeOf([]AgentResourceApprovalSummary{}) ||
pendingApprovalsField.Tag.Get("json") != "pendingApprovals" {
t.Error("AgentResourceContext must carry PendingApprovals as a stable []AgentResourceApprovalSummary field")
}
contextSectionsField, ok := contextType.FieldByName("ContextSections")
if !ok {
t.Fatal("AgentResourceContext must carry ContextSections")
}
if contextSectionsField.Type != reflect.TypeOf([]AgentResourceContextSection{}) ||
contextSectionsField.Tag.Get("json") != "contextSections" {
t.Error("AgentResourceContext must carry ContextSections as a stable []AgentResourceContextSection field")
}
if !strings.Contains(src, "type AgentResourceContextRedaction = agentcontext.Redaction") ||
!strings.Contains(src, "type AgentResourceContextFact = agentcontext.Fact") {
t.Error("AgentResourceContext context sections must expose the shared typed fact and redaction metadata")
}
}
// TestContract_AgentResourceContextWiresApprovalsProvider pins the
// startup-time wire-up that lets the bundle endpoint surface pending
// approvals scoped to a resource. Drift here means the field always
// shows up as an empty array even when there ARE pending approvals,
// silently breaking the substrate's "everything an agent needs in
// one read" contract for the governance dimension.
//
// The wire-up has two halves: router.go installs the provider with
// a provider that delegates to named functions in
// agent_resource_context.go, and those functions apply the org and
// resource filters while supporting the fleet endpoint's bulk count
// projection. Both halves are pinned here so a refactor that moves
// the filter logic still passes the test only if the resulting code
// preserves the safety and scale properties.
func TestContract_AgentResourceContextWiresApprovalsProvider(t *testing.T) {
router, err := os.ReadFile("router.go")
if err != nil {
t.Fatalf("read router.go: %v", err)
}
if !strings.Contains(string(router), "r.agentContextHandler.SetApprovalsProvider(agentApprovalStoreProvider{})") {
t.Error("router.go must wire the approvals provider on the agent context handler so the bundle's pendingApprovals section is populated")
}
if !strings.Contains(string(router), "approval.SetStore") {
t.Error("router.go must keep documenting that the agent approval provider resolves the process-global approval store at request time")
}
bundle, err := os.ReadFile("agent_resource_context.go")
if err != nil {
t.Fatalf("read agent_resource_context.go: %v", err)
}
if !strings.Contains(string(bundle), "type agentApprovalStoreProvider struct{}") {
t.Error("agent_resource_context.go must define the request-time approval store provider that router.go wires")
}
if !strings.Contains(string(bundle), "return pendingApprovalsForResourceFromStore(approval.GetStore(), resourceID, orgID)") {
t.Error("agentApprovalStoreProvider must resolve the approval store at request time for per-resource context reads")
}
if !strings.Contains(string(bundle), "return pendingApprovalCountsByResourceFromStore(approval.GetStore(), orgID)") {
t.Error("agentApprovalStoreProvider must use the bulk count helper for fleet context reads")
}
if !strings.Contains(string(bundle), "func pendingApprovalsForResourceFromStore(") {
t.Error("agent_resource_context.go must define pendingApprovalsForResourceFromStore as the named, testable filter — drift here breaks the cross-org / cross-resource isolation pins")
}
if !strings.Contains(string(bundle), "func pendingApprovalCountsByResourceFromStore(") {
t.Error("agent_resource_context.go must define pendingApprovalCountsByResourceFromStore so fleet context counts are grouped by one bounded pending-approval scan")
}
if !strings.Contains(string(bundle), "if !approval.BelongsToOrg(req, orgID)") {
t.Error("the named filters must scope pending approvals via BelongsToOrg so cross-tenant requests don't leak into an agent context bundle")
}
if !strings.Contains(string(bundle), "if req.CanonicalResourceID() != resourceID") {
t.Error("the named filter must filter pending approvals by CanonicalResourceID so the bundle only carries approvals targeting the requested resource")
}
if !strings.Contains(string(bundle), "counts[resourceID]++") {
t.Error("the bulk count helper must group pending approvals by canonical resource id instead of forcing fleet context into per-resource scans")
}
}
// TestContract_GetResourceContextCapabilityListsPendingApprovals pins
// the discovery contract: the capabilities manifest must mention
// pendingApprovals under get_resource_context so an agent reading
// the manifest learns the bundle now carries the governance
// dimension. Drift here means external agents still expect the
// pre-slice-45 shape.
func TestContract_GetResourceContextCapabilityListsPendingApprovals(t *testing.T) {
src := readAgentCapabilitiesManifestSource(t)
if !strings.Contains(src, "pending approvals scoped to this resource") {
t.Error("get_resource_context description must mention pending approvals so agents discover the bundle's new governance section through the manifest")
}
}
func TestContract_AgentCommandPayloadsRequireActionScope(t *testing.T) {
source, err := os.ReadFile("agent_command_redaction.go")
if err != nil {
t.Fatalf("read agent_command_redaction.go: %v", err)
}
src := string(source)
if !strings.Contains(src, "requestCanReadAgentCommandPayloads") {
t.Error("agent command redaction must stay behind a named request-scope helper")
}
if !strings.Contains(src, "token.HasScope(config.ScopeAIExecute)") {
t.Error("agent command payloads must require ai:execute on API-token callers, not only monitoring:read")
}
if !strings.Contains(src, "redactAgentEventCommandsForRequest") {
t.Error("agent SSE events must pass through command redaction before serialization")
}
if !strings.Contains(src, "redactAgentResourceContextCommandsForRequest") {
t.Error("agent resource-context bundles must pass through command redaction before serialization")
}
}
// TestContract_AgentFleetContextEndpointSurfacesStableShape pins the
// agent-paradigm fleet view contract: the endpoint must return a
// `resources` array (never null) so agents iterate without
// nil-checking, must initialize per-severity counts as a struct (not
// a map) so agents branch on stable field names, and must carry the
// operator-intent flags (intentionallyOffline, neverAutoRemediate,
// maintenanceWindowActive) that gate auto-action. Drift here would
// silently break the substrate's "where do I focus?" answer.
func TestContract_AgentFleetContextEndpointSurfacesStableShape(t *testing.T) {
source, err := os.ReadFile("agent_resource_context.go")
if err != nil {
t.Fatalf("read agent_resource_context.go: %v", err)
}
src := string(source)
if !strings.Contains(src, "Resources: make([]AgentFleetResourceSummary, 0, len(resources))") {
t.Error("AgentFleetContext.Resources must default to a non-nil empty slice — agents iterate without nil-checks")
}
if !strings.Contains(src, "Resources []AgentFleetResourceSummary `json:\"resources\"`") {
t.Error("AgentFleetContext must carry Resources as a stable []AgentFleetResourceSummary field")
}
if !strings.Contains(src, "IntentionallyOffline bool `json:\"intentionallyOffline\"`") {
t.Error("AgentFleetResourceSummary must carry IntentionallyOffline so agents see operator lock at a glance")
}
if !strings.Contains(src, "NeverAutoRemediate bool `json:\"neverAutoRemediate\"`") {
t.Error("AgentFleetResourceSummary must carry NeverAutoRemediate")
}
if !strings.Contains(src, "MaintenanceWindowActive bool `json:\"maintenanceWindowActive\"`") {
t.Error("AgentFleetResourceSummary must carry MaintenanceWindowActive — server pre-computes so agents don't re-evaluate timestamps client-side")
}
if !strings.Contains(src, "Findings AgentFleetFindingCounts `json:\"findings\"`") {
t.Error("AgentFleetResourceSummary must carry per-severity finding counts as a struct so agents branch on stable field names")
}
if !strings.Contains(src, "PendingApprovalCount int `json:\"pendingApprovalCount\"`") {
t.Error("AgentFleetResourceSummary must carry PendingApprovalCount so agents see governance-blocked resources at a glance")
}
}
func TestContract_AgentFleetDiagnosticsEndpointSurfacesStableShape(t *testing.T) {
handler, err := os.ReadFile("agent_fleet_doctor.go")
if err != nil {
t.Fatalf("read agent_fleet_doctor.go: %v", err)
}
router, err := os.ReadFile("router_routes_registration.go")
if err != nil {
t.Fatalf("read router_routes_registration.go: %v", err)
}
monitoringSource, err := os.ReadFile("../monitoring/agent_fleet_doctor.go")
if err != nil {
t.Fatalf("read monitoring agent_fleet_doctor.go: %v", err)
}
handlerSrc := string(handler)
routerSrc := string(router)
monitoringSrc := string(monitoringSource)
if !strings.Contains(routerSrc, `"/api/agents/diagnostics"`) ||
!strings.Contains(routerSrc, `RequireAdmin(r.config, RequireScope(config.ScopeSettingsRead, r.handleAgentFleetDiagnostics))`) {
t.Error("agent fleet diagnostics route must remain admin settings:read only")
}
if !strings.Contains(handlerSrc, "GetAgentFleetDiagnostics(serverVersion, time.Now().UTC())") {
t.Error("agent fleet diagnostics handler must delegate to the monitoring-owned read-only producer")
}
for _, required := range []string{
"GeneratedAt int64 `json:\"generatedAt\"`",
"ServerVersion string `json:\"serverVersion,omitempty\"`",
"Summary AgentFleetDiagnosticSummary `json:\"summary\"`",
"Agents []AgentFleetAgentDiagnostic `json:\"agents\"`",
"Reasons []AgentFleetDiagnosticReason `json:\"reasons\"`",
"RepairActions []AgentFleetDiagnosticRepair `json:\"repairActions,omitempty\"`",
} {
if !strings.Contains(monitoringSrc, required) {
t.Errorf("agent fleet diagnostics payload missing stable field %q", required)
}
}
}
// TestContract_AgentOperationsLoopStatusEndpointSurfacesStableShape pins the
// content-safe loop-status wire shape external agents use before choosing
// fleet, resource, finding, or action tools.
func TestContract_AgentOperationsLoopStatusEndpointSurfacesStableShape(t *testing.T) {
source, err := os.ReadFile("agent_resource_context.go")
if err != nil {
t.Fatalf("read agent_resource_context.go: %v", err)
}
src := string(source)
required := []string{
"Steps: []AgentOperationsLoopStep{}",
"NextAction string `json:\"nextAction\"`",
"ProgressLabel string `json:\"progressLabel\"`",
"Steps []AgentOperationsLoopStep `json:\"steps\"`",
"PatrolEvidenceCount int `json:\"patrolEvidenceCount\"`",
"PatrolIssueEvidenceCount int `json:\"patrolIssueEvidenceCount\"`",
"ActiveFindingCount int `json:\"activeFindingCount\"`",
"PendingApprovalCount int `json:\"pendingApprovalCount\"`",
"GovernedActionCount int `json:\"governedActionCount\"`",
"ApprovedDecisionCount int `json:\"approvedDecisionCount\"`",
"RejectedDecisionCount int `json:\"rejectedDecisionCount\"`",
"VerifiedOutcomeCount int `json:\"verifiedOutcomeCount\"`",
"OperationsLoopStarterCount int `json:\"operationsLoopStarterCount\"`",
"AssistantOperationsLoopStarterCount int `json:\"assistantOperationsLoopStarterCount\"`",
"PatrolOperationsLoopStarterCount int `json:\"patrolOperationsLoopStarterCount\"`",
"PatrolControlLoopStarterCount int `json:\"patrolControlOperationsLoopStarterCount\"`",
"PatrolControlCompletedLoopCount int `json:\"patrolControlCompletedOperationsLoopCount\"`",
"PatrolControlResolvedLoopCount int `json:\"patrolControlResolvedOperationsLoopCount\"`",
"PatrolControlValueState string `json:\"patrolControlValueState\"`",
"PatrolAutonomyLoopStarterCount int `json:\"patrolAutonomyOperationsLoopStarterCount\"`",
"PatrolAutonomyCompletedLoopCount int `json:\"patrolAutonomyCompletedOperationsLoopCount\"`",
"PatrolAutonomyResolvedLoopCount int `json:\"patrolAutonomyResolvedOperationsLoopCount\"`",
"PatrolAutonomyValueState string `json:\"patrolAutonomyValueState\"`",
"ProActivationLoopStarterCount int `json:\"proActivationOperationsLoopStarterCount\"`",
"ProActivationCompletedLoopCount int `json:\"proActivationCompletedOperationsLoopCount\"`",
"ProActivationResolvedLoopCount int `json:\"proActivationResolvedOperationsLoopCount\"`",
"ProActivationValueProofState string `json:\"proActivationValueProofState\"`",
"MCPOperationsLoopStarterCount int `json:\"mcpOperationsLoopStarterCount\"`",
"ExternalAgentReady bool `json:\"externalAgentReady\"`",
"WindowStart time.Time `json:\"windowStart\"`",
"GeneratedAt time.Time `json:\"generatedAt\"`",
"External-agent readiness is a separate capability signal,",
"manifest := agentcapabilities.CanonicalManifest()",
"ExternalAgentReady: h.agentOperationsLoopExternalAgentReady(r.Context(), manifest, now)",
"func agentOperationsLoopExternalAgentTokenReady(manifest agentcapabilities.Manifest, tokens []config.APITokenRecord, now time.Time) bool",
"agentcapabilities.RequiredCapabilityScopes(",
"coversLoop := true",
"if !token.HasScope(scope) {",
`store.GetActionLifecycleEvents("", since, 0)`,
"return pulseIntelligenceActionWasApproved(record) || pulseIntelligenceActionWasRejected(record)",
"return pulseIntelligenceActionVerifiedOutcome(record)",
"governanceStepCount := status.PendingApprovalCount",
"decisionCount := status.ApprovedDecisionCount + status.RejectedDecisionCount",
"verificationStepCount := status.VerifiedOutcomeCount",
"verificationStepCount = status.RejectedDecisionCount",
"patrolControlProof := agentOperationsLoopPatrolControlProof(status)",
"status.PatrolControlLoopStarterCount = starterCounts.patrolControl",
"status.PatrolAutonomyLoopStarterCount = status.PatrolControlLoopStarterCount",
"status.PatrolControlValueState = patrolControlProof.ValueProofState",
"status.PatrolAutonomyValueState = status.PatrolControlValueState",
"status.ProActivationValueProofState = status.PatrolControlValueState",
"case config.WorkflowPromptActivitySurfacePatrolControl:",
"case config.WorkflowPromptActivitySurfacePatrolAutonomy:",
"counts.patrolControl++",
"return telemetry.ClassifyPulseIntelligencePatrolControlProof(telemetry.PulseIntelligencePatrolControlProofInput{",
"ContextualCollaborationCount: agentOperationsLoopContextualCollaborationCount(status)",
"type AgentAggregateFindingsProvider interface {",
"ActiveFindingCount() int",
"if aggregateProvider, ok := h.findingsProvider.(AgentAggregateFindingsProvider); ok {",
"aggregateProvider.ActiveFindingCount()",
"case status.PendingApprovalCount > 0:",
"Review pending Patrol approvals before treating previous verified work as current.",
"case status.ActiveFindingCount > 0 && hasVerifiedOutcome:",
"Open Assistant on the active Patrol finding before treating previous verified work as current.",
"Review active Patrol findings before treating previous verified work as current.",
}
for _, fragment := range required {
if !strings.Contains(src, fragment) {
t.Errorf("AgentOperationsLoopStatus contract missing %s", fragment)
}
}
if strings.Contains(src, "ExternalAgentReady: status.ExternalAgentReady") {
t.Error("AgentOperationsLoopStatus must not pass MCP readiness into Patrol control proof classification")
}
if strings.Contains(src, `ID: "external_agents"`) {
t.Error("AgentOperationsLoopStatus steps must not make optional MCP readiness a Patrol operator step")
}
}
// TestContract_ActionCompletedPayloadCarriesVerification pins that
// the action.completed SSE payload exposes the verification block
// — the agent-stable projection of the post-execution probe so
// agents close the "did it actually work?" loop without a follow-up
// audit fetch. Drift here forces every agent watching the stream to
// poll /api/actions/{id} after every dispatch, defeating the
// substrate's push-notification guarantee for dispatch certainty.
func TestContract_ActionCompletedPayloadCarriesVerification(t *testing.T) {
source, err := os.ReadFile("agent_events.go")
if err != nil {
t.Fatalf("read agent_events.go: %v", err)
}
src := string(source)
if !strings.Contains(src, "Verification *AgentResourceActionVerification `json:\"verification,omitempty\"`") {
t.Error("AgentEventActionCompletedPayload must carry Verification as a *AgentResourceActionVerification — agents close the certainty loop on this field")
}
}
// TestContract_RouterBridgesVerificationOntoActionCompleted pins
// that the router-side bridge actually carries the verification
// projection from the canonical top-level verification helper onto
// the SSE payload.
// The payload field exists is one half of the contract; this is the
// other half — that the bridge populates it when the underlying
// audit has a verification result.
func TestContract_RouterBridgesVerificationOntoActionCompleted(t *testing.T) {
source, err := os.ReadFile("router.go")
if err != nil {
t.Fatalf("read router.go: %v", err)
}
src := string(source)
if !strings.Contains(src, "if v := projectAgentResourceVerification(unifiedresources.CanonicalActionVerification(record)); v != nil {") {
t.Error("router.go must project canonical action verification onto the action.completed payload via projectAgentResourceVerification — drift here means verification reaches the audit store but never the SSE stream")
}
if !strings.Contains(src, "payload.Verification = v") {
t.Error("router.go must assign the projected verification onto payload.Verification")
}
}
// TestContract_AgentResourceActionSummaryCarriesVerification pins
// that the resource-context bundle's recent-actions surface also
// carries verification, paralleling the SSE payload. Symmetry
// here matters: an agent should get the same verification fact
// whether they read the bundle (depth) or watch the doorbell
// (push). Drift would let one surface ship a feature the other
// doesn't.
func TestContract_AgentResourceActionSummaryCarriesVerification(t *testing.T) {
source, err := os.ReadFile("agent_resource_context.go")
if err != nil {
t.Fatalf("read agent_resource_context.go: %v", err)
}
src := string(source)
if !strings.Contains(src, "Verification *AgentResourceActionVerification `json:\"verification,omitempty\"`") {
t.Error("AgentResourceActionSummary must carry Verification so the bundle's recent-actions and the action.completed SSE payload speak the same vocabulary")
}
if !strings.Contains(src, "func projectAgentResourceVerification(v *unified.ActionVerificationResult)") {
t.Error("projectAgentResourceVerification must exist as the shared helper — both the SSE bridge and the bundle's projector route through it so the wire shape cannot drift between surfaces")
}
if !strings.Contains(src, "projectAgentResourceVerification(unified.CanonicalActionVerification(audit))") {
t.Error("AgentResourceActionSummary must project canonical action verification, not only legacy result.verification")
}
}
// TestContract_PulseIntelligenceApprovedExecutionTelemetryUsesActionLifecycle
// pins that the paid-value approved execution counter is sourced from
// action lifecycle evidence and only then resolved back to the action audit
// approval state. Drift here would let generic planning, approval, or token
// use masquerade as a completed operations loop.
func TestContract_PulseIntelligenceApprovedExecutionTelemetryUsesActionLifecycle(t *testing.T) {
source, err := os.ReadFile("telemetry_pulse_intelligence.go")
if err != nil {
t.Fatalf("read telemetry_pulse_intelligence.go: %v", err)
}
src := string(source)
for _, fragment := range []string{
`store.GetActionLifecycleEvents("", since, 0)`,
`store.GetActionAudit(actionID)`,
`pulseIntelligenceActionWasApproved(record)`,
`pulseIntelligenceApprovedActionSuccess(record)`,
`case unifiedresources.ActionStateExecuting, unifiedresources.ActionStateCompleted, unifiedresources.ActionStateFailed:`,
`snapshot.ApprovedActionAttempts30d += len(approvedAttemptIDs)`,
`snapshot.ApprovedActionSuccesses30d += len(approvedSuccessIDs)`,
} {
if !strings.Contains(src, fragment) {
t.Errorf("Pulse Intelligence approved execution telemetry must stay lifecycle-backed and audit-approved; missing %s", fragment)
}
}
}
// TestContract_PulseIntelligenceApprovedDecisionTelemetryUsesActionDecisionEvidence
// pins that approved governed-action decisions are counted separately from
// approved execution attempts, so completed operations-loop proof can mean an
// approve/reject decision without pretending every approval executed.
func TestContract_PulseIntelligenceApprovedDecisionTelemetryUsesActionDecisionEvidence(t *testing.T) {
source, err := os.ReadFile("telemetry_pulse_intelligence.go")
if err != nil {
t.Fatalf("read telemetry_pulse_intelligence.go: %v", err)
}
src := string(source)
for _, fragment := range []string{
`approvedDecisionIDs := pulseIntelligenceApprovedActionDecisionIDs(store, orgID, since)`,
`if event.State != unifiedresources.ActionStateApproved`,
`pulseIntelligenceActionWasApprovedSince(record, since)`,
`if approval.Outcome != unifiedresources.OutcomeApproved`,
`snapshot.ApprovedActionDecisions30d += len(approvedDecisionIDs)`,
} {
if !strings.Contains(src, fragment) {
t.Errorf("Pulse Intelligence approved decision telemetry must stay decision-backed and separate from execution attempts; missing %s", fragment)
}
}
}
// TestContract_PulseIntelligenceApprovedSuccessTelemetryRequiresVerifiedOutcome
// pins that outbound Pulse Intelligence approved-success telemetry and the
// operations-loop status route share the same verified-outcome predicate.
// A completed action result alone is execution evidence, not post-action proof
// for Patrol control resolved-loop reporting.
func TestContract_PulseIntelligenceApprovedSuccessTelemetryRequiresVerifiedOutcome(t *testing.T) {
telemetrySource, err := os.ReadFile("telemetry_pulse_intelligence.go")
if err != nil {
t.Fatalf("read telemetry_pulse_intelligence.go: %v", err)
}
telemetrySrc := string(telemetrySource)
for _, fragment := range []string{
`if record.State != unifiedresources.ActionStateCompleted`,
`return pulseIntelligenceActionVerifiedOutcome(record)`,
`func pulseIntelligenceActionVerifiedOutcome(record unifiedresources.ActionAuditRecord) bool`,
`outcome := unifiedresources.NormalizeVerificationOutcome(record.VerificationOutcome)`,
`outcome.Status == unifiedresources.VerificationVerified`,
`verification := unifiedresources.CanonicalActionVerification(record)`,
`return verification != nil && verification.Ran && verification.Success`,
} {
if !strings.Contains(telemetrySrc, fragment) {
t.Errorf("Pulse Intelligence approved-success telemetry must require verified outcome proof; missing %s", fragment)
}
}
if strings.Contains(telemetrySrc, `return record.Result == nil || record.Result.Success`) {
t.Error("Pulse Intelligence approved-success telemetry must not treat a bare successful execution result as verified outcome proof")
}
statusSource, err := os.ReadFile("agent_resource_context.go")
if err != nil {
t.Fatalf("read agent_resource_context.go: %v", err)
}
if !strings.Contains(string(statusSource), `return pulseIntelligenceActionVerifiedOutcome(record)`) {
t.Error("operations-loop status verified outcome count must use the shared Pulse Intelligence verified-outcome predicate")
}
}
// TestContract_PulseIntelligenceRejectedDecisionTelemetryUsesActionLifecycle
// pins that rejected governed-action decisions are counted from decision
// lifecycle evidence and remain separate from approved execution proof.
func TestContract_PulseIntelligenceRejectedDecisionTelemetryUsesActionLifecycle(t *testing.T) {
source, err := os.ReadFile("telemetry_pulse_intelligence.go")
if err != nil {
t.Fatalf("read telemetry_pulse_intelligence.go: %v", err)
}
src := string(source)
for _, fragment := range []string{
`rejectedDecisionIDs := pulseIntelligenceRejectedActionDecisionIDs(store, orgID, since)`,
`if event.State != unifiedresources.ActionStateRejected`,
`store.GetActionAudit(actionID)`,
`pulseIntelligenceActionWasRejected(record)`,
`snapshot.RejectedActionDecisions30d += len(rejectedDecisionIDs)`,
} {
if !strings.Contains(src, fragment) {
t.Errorf("Pulse Intelligence rejected decision telemetry must stay lifecycle-backed and audit-rejected; missing %s", fragment)
}
}
}
// TestContract_OperatorStateWriteServerPopulatesAttribution pins the
// audit-honesty contract for the agent surface's only write loop:
// the PUT handler must populate SetAt and SetBy from the server,
// ignoring any client-supplied values. Drift here would let an
// agent (or a misconfigured client) spoof attribution timestamps,
// breaking the audit trail's trust model.
func TestContract_OperatorStateWriteServerPopulatesAttribution(t *testing.T) {
source, err := os.ReadFile("resources_operator_state.go")
if err != nil {
t.Fatalf("read resources_operator_state.go: %v", err)
}
src := string(source)
if !strings.Contains(src, "SetAt: time.Now().UTC(),") {
t.Error("operator-state PUT must populate SetAt with the server clock — client cannot spoof attribution")
}
if !strings.Contains(src, "SetBy: getUserID(r),") {
t.Error("operator-state PUT must populate SetBy from the authenticated identity — client cannot spoof who-did-it")
}
if !strings.Contains(src, "// canonical_id from URL wins over body") {
t.Error("operator-state PUT must take the canonical id from the URL, not the body — drift would let scope-confusion writes succeed")
}
}
// TestContract_OperatorStateWriteEmitsStableErrorTokens pins that
// the only two error codes the manifest declares for set/get
// operator-state actually reach the wire from the handler. Drift
// here means external agents branching on operator_state_not_set
// or operator_state_invalid would silently miss the relevant
// failures — manifest claims a code that the handler never emits.
func TestContract_OperatorStateWriteEmitsStableErrorTokens(t *testing.T) {
source, err := os.ReadFile("resources_operator_state.go")
if err != nil {
t.Fatalf("read resources_operator_state.go: %v", err)
}
src := string(source)
if !strings.Contains(src, "agentcapabilities.AgentErrCodeOperatorStateNotSet") {
t.Error("GET handler must emit operator_state_not_set when no entry exists — manifest declares this code")
}
if !strings.Contains(src, "agentcapabilities.AgentErrCodeOperatorStateInvalid") {
t.Error("PUT handler must emit operator_state_invalid on validation failure — manifest declares this code")
}
if !strings.Contains(src, "errors.Is(err, unified.ErrResourceOperatorStateInvalid)") {
t.Error("PUT handler must branch on ErrResourceOperatorStateInvalid so domain validation errors map to the stable wire token — string-matching the error message would drift")
}
}
// TestContract_AgentSurfaceErrorCodesMatchManifestDeclarations pins
// the symmetry between what handlers actually emit and what the
// capabilities manifest declares. The doc says the manifest's
// errorCodes list is "the closed set of values the error field may
// carry on failure"; this test enforces that claim by reading every
// writeJSONError call from the two agent-surface handler files and
// asserting each emitted code is either (a) declared by the matching
// capability, or (b) one of the three cross-cutting codes the auth
// middleware emits universally. Drift in either direction is a
// contract regression — emitting an undeclared code silently breaks
// agents that branch on the closed set; declaring a code the
// handler never emits misleads agents into writing dead-code paths.
func TestContract_AgentSurfaceErrorCodesMatchManifestDeclarations(t *testing.T) {
// Codes emitted by the multi-tenant / auth middleware that apply
// to every authenticated endpoint and are intentionally not
// duplicated on per-capability errorCodes lists. Documented in
// api-contracts.md "cross-cutting codes" paragraph.
crossCutting := map[string]bool{
"invalid_org": true,
"org_suspended": true,
"access_denied": true,
}
// Handler files that back agent-surface capabilities. If a future
// capability adds a new file, append it here so the audit follows.
handlerFiles := []string{
"agent_resource_context.go",
"resources_operator_state.go",
"actions.go",
"ai_handlers.go",
}
// Codes the action handlers emit that are deliberately NOT in
// the per-capability manifest entries: 5xx internal-failure
// codes (audit-store outages, encode failures) and codes that
// surface from edge paths the manifest declares against a
// different capability. Agents branch on 5xx generically; they
// don't need the specific token. Whitelisted here so the
// emit/declare audit doesn't false-positive on them.
internalOnlyCodes := map[string]bool{
"action_audit_unavailable": true,
"action_audit_persist_failed": true,
"action_plan_failed": true,
"action_plan_encode_failed": true,
"action_audit_query_failed": true,
"action_decision_persist_failed": true,
"action_decision_encode_failed": true,
"action_decision_failed": true,
"action_execution_persist_failed": true,
"action_execution_encode_failed": true,
"action_execution_failed": true,
"action_not_executing": true,
"action_policy_validation_failed": true,
"action_plan_validation_failed": true,
"resource_registry_unavailable": true,
}
agentErrorConstantValues := map[string]string{
"AgentErrCodeResourceNotFound": agentcapabilities.AgentErrCodeResourceNotFound,
"AgentErrCodeOperatorStateNotSet": agentcapabilities.AgentErrCodeOperatorStateNotSet,
"AgentErrCodeOperatorStateInvalid": agentcapabilities.AgentErrCodeOperatorStateInvalid,
"AgentErrCodeInvalidFindingRequest": agentcapabilities.AgentErrCodeInvalidFindingRequest,
"AgentErrCodeFindingNotFound": agentcapabilities.AgentErrCodeFindingNotFound,
"AgentErrCodeFindingActionNotAllowed": agentcapabilities.AgentErrCodeFindingActionNotAllowed,
"AgentErrCodePatrolUnavailable": agentcapabilities.AgentErrCodePatrolUnavailable,
"AgentErrCodeInvalidActionRequest": agentcapabilities.AgentErrCodeInvalidActionRequest,
"AgentErrCodeCapabilityNotFound": agentcapabilities.AgentErrCodeCapabilityNotFound,
"AgentErrCodeActionExecutionUnavailable": agentcapabilities.AgentErrCodeActionExecutionUnavailable,
"AgentErrCodeMissingID": agentcapabilities.AgentErrCodeMissingID,
"AgentErrCodeInvalidID": agentcapabilities.AgentErrCodeInvalidID,
"AgentErrCodeInvalidActionDecision": agentcapabilities.AgentErrCodeInvalidActionDecision,
"AgentErrCodeActionNotFound": agentcapabilities.AgentErrCodeActionNotFound,
"AgentErrCodeActionNotPending": agentcapabilities.AgentErrCodeActionNotPending,
"AgentErrCodeActionPlanExpired": agentcapabilities.AgentErrCodeActionPlanExpired,
"AgentErrCodeInvalidActionExecution": agentcapabilities.AgentErrCodeInvalidActionExecution,
"AgentErrCodeActionNotApproved": agentcapabilities.AgentErrCodeActionNotApproved,
"AgentErrCodeActionAlreadyExecuting": agentcapabilities.AgentErrCodeActionAlreadyExecuting,
"AgentErrCodeActionExecutionFinal": agentcapabilities.AgentErrCodeActionExecutionFinal,
"AgentErrCodeActionDryRunOnly": agentcapabilities.AgentErrCodeActionDryRunOnly,
"AgentErrCodeActionPlanDrift": agentcapabilities.AgentErrCodeActionPlanDrift,
"AgentErrCodeResourceRemediationLocked": agentcapabilities.AgentErrCodeResourceRemediationLocked,
"AgentErrCodeActionExecutorUnavailable": agentcapabilities.AgentErrCodeActionExecutorUnavailable,
}
// Extract every emitted shared code from writeJSONError /
// writeJSONErrorWithDetails calls. Agent-surface codes must flow
// through agentcapabilities.AgentErrCode* constants so the manifest and
// handlers cannot drift by editing duplicate literals.
emitted := map[string]bool{}
for _, file := range handlerFiles {
body, err := os.ReadFile(file)
if err != nil {
t.Fatalf("read %s: %v", file, err)
}
constantRE := regexp.MustCompile(`(?s)writeJSONError(?:WithDetails)?\(\s*w\s*,\s*[^,]+,\s*agentcapabilities\.(AgentErrCode[A-Za-z0-9]+)`)
for _, m := range constantRE.FindAllSubmatch(body, -1) {
constantName := string(m[1])
code, ok := agentErrorConstantValues[constantName]
if !ok {
t.Errorf("%s emits unknown shared agent error constant %s", file, constantName)
continue
}
emitted[code] = true
}
literalRE := regexp.MustCompile(`(?s)writeJSONError(?:WithDetails)?\(\s*w\s*,\s*[^,]+,\s*"([a-z_][a-z_0-9]+)"`)
for _, m := range literalRE.FindAllSubmatch(body, -1) {
code := string(m[1])
if internalOnlyCodes[code] || crossCutting[code] {
continue
}
t.Errorf("%s emits agent-surface error code %q as a local literal; use an agentcapabilities.AgentErrCode* constant", file, code)
}
}
if len(emitted) == 0 {
t.Fatal("no shared writeJSONError emissions found in agent-surface handler files; the audit regex or handler constants must be wrong")
}
// Pull declared codes from the runtime manifest so the test follows
// constant-backed manifest edits without source scraping.
declared := map[string]bool{}
for _, cap := range agentcapabilities.CanonicalManifest().Capabilities {
for _, code := range cap.ErrorCodes {
declared[code] = true
}
}
// Every emitted code must be either declared somewhere in the
// manifest OR a cross-cutting code.
for code := range emitted {
if declared[code] || crossCutting[code] {
continue
}
t.Errorf("handler emits %q but no capability in the manifest declares it and it is not a documented cross-cutting code — drift here breaks agents that branch on the closed set", code)
}
// Every manifest-declared code must have a matching emission
// somewhere in the agent-surface handlers, otherwise agents
// follow a code path the substrate never walks.
for code := range declared {
if !emitted[code] {
t.Errorf("manifest declares %q but no agent-surface handler emits it — declaring a dead code misleads agents into writing branches the substrate never triggers", code)
}
}
}
func TestContract_PlanActionDeclaresExecutionUnavailable(t *testing.T) {
manifest := agentcapabilities.CanonicalManifest()
var planAction agentcapabilities.Capability
for _, cap := range manifest.Capabilities {
if cap.Name == agentcapabilities.PlanActionCapabilityName {
planAction = cap
break
}
}
if planAction.Name == "" {
t.Fatalf("agent capabilities manifest must declare %s", agentcapabilities.PlanActionCapabilityName)
}
if !stringSliceContains(planAction.ErrorCodes, agentcapabilities.AgentErrCodeActionExecutionUnavailable) {
t.Error("plan_action must declare action_execution_unavailable so agents can branch on executor-owned live-readiness refusal")
}
}
func TestContract_ResourceActionReadinessPayloadShape(t *testing.T) {
payload, err := json.Marshal(unifiedresources.Resource{
ID: "app-container:docker:web",
Type: unifiedresources.ResourceTypeAppContainer,
Name: "web",
Status: unifiedresources.StatusOnline,
LastSeen: time.Date(2026, 6, 12, 12, 0, 0, 0, time.UTC),
Sources: []unifiedresources.DataSource{unifiedresources.SourceDocker},
ActionReadiness: []unifiedresources.ResourceActionReadiness{
{
Name: "restart",
Available: false,
ReasonCode: "command_agent_disconnected",
Reason: "Docker / Podman command agent is not connected.",
},
},
})
if err != nil {
t.Fatalf("marshal resource: %v", err)
}
body := string(payload)
for _, want := range []string{
`"actionReadiness":[`,
`"name":"restart"`,
`"available":false`,
`"reasonCode":"command_agent_disconnected"`,
`"reason":"Docker / Podman command agent is not connected."`,
} {
if !strings.Contains(body, want) {
t.Fatalf("resource action readiness payload missing %s: %s", want, body)
}
}
if strings.Contains(body, "InternalHandler") {
t.Fatalf("resource payload leaked internal action handler: %s", body)
}
}
func TestContract_DockerLifecycleActionsResolveCommandAgentAndDispatchTrusted(t *testing.T) {
source, err := os.ReadFile("docker_container_action_executor.go")
if err != nil {
t.Fatalf("read docker_container_action_executor.go: %v", err)
}
src := string(source)
sharedSource, err := os.ReadFile("action_executor.go")
if err != nil {
t.Fatalf("read action_executor.go: %v", err)
}
sharedSrc := string(sharedSource)
for _, snippet := range []string{
"func (e dockerContainerActionExecutor) connectedDockerCommandAgentID(resource unified.Resource) (string, error)",
"resource.Docker.AgentID",
"e.agents.GetAgentForHost(strings.TrimSpace(resource.Docker.Hostname))",
"Trusted: true",
} {
if !strings.Contains(src, snippet) {
t.Fatalf("docker lifecycle executor must pin command-agent/trusted dispatch snippet %q", snippet)
}
}
if !strings.Contains(sharedSrc, "GetAgentForHost(hostname string) (string, bool)") {
t.Fatal("shared action command interface must keep hostname-based command-agent resolution available")
}
if strings.Index(src, "if agentID := strings.TrimSpace(resource.Docker.AgentID)") >
strings.Index(src, "e.agents.GetAgentForHost(strings.TrimSpace(resource.Docker.Hostname))") {
t.Fatal("docker lifecycle executor must try the Docker reporting agent id before falling back to hostname resolution")
}
}
func TestContract_ProxmoxLifecycleActionsResolveNodeCommandAgentAndVerifyState(t *testing.T) {
source, err := os.ReadFile("proxmox_guest_action_executor.go")
if err != nil {
t.Fatalf("read proxmox_guest_action_executor.go: %v", err)
}
src := string(source)
for _, snippet := range []string{
"func (e proxmoxGuestActionExecutor) connectedProxmoxNodeCommandAgentID(resource unified.Resource) (string, error)",
"resource.Proxmox.LinkedAgentID",
"e.agents.GetAgentForHost(strings.TrimSpace(resource.Proxmox.NodeName))",
"Trusted: true",
"func (e proxmoxGuestActionExecutor) verifyProxmoxGuestState(",
"proxmoxGuestStatusCommand(kind, vmid)",
"parseProxmoxGuestStatus(lastOutput) == expected",
} {
if !strings.Contains(src, snippet) {
t.Fatalf("proxmox lifecycle executor must pin command-agent/trusted verification snippet %q", snippet)
}
}
if strings.Index(src, "if agentID := strings.TrimSpace(resource.Proxmox.LinkedAgentID)") >
strings.Index(src, "e.agents.GetAgentForHost(strings.TrimSpace(resource.Proxmox.NodeName))") {
t.Fatal("proxmox lifecycle executor must try the linked Proxmox node agent before falling back to node hostname resolution")
}
router, err := os.ReadFile("router.go")
if err != nil {
t.Fatalf("read router.go: %v", err)
}
routerSrc := string(router)
for _, snippet := range []string{
"newRoutedActionExecutor(",
"newDockerContainerActionExecutor(r.resourceHandlers, r.agentExecServer)",
"newProxmoxGuestActionExecutor(r.resourceHandlers, r.agentExecServer)",
} {
if !strings.Contains(routerSrc, snippet) {
t.Fatalf("router must register Docker and Proxmox action executors through routed action executor; missing %q", snippet)
}
}
}
// TestContract_AgentCapabilitiesManifestIsPublic pins the auth
// contract for the discovery surface: the manifest must be in the
// router's publicPaths list so it serves without a token. The
// manifest's purpose is "let an agent introspect Pulse before it
// has credentials" — gating the manifest behind the same
// monitoring:read scope that gates everything else makes
// discovery a chicken-and-egg problem. Slice 40 had this wrong;
// slice 47 fixed it. Pin so it cannot regress silently.
func TestContract_AgentCapabilitiesManifestIsPublic(t *testing.T) {
source, err := os.ReadFile("router.go")
if err != nil {
t.Fatalf("read router.go: %v", err)
}
src := string(source)
if !strings.Contains(src, "\"/api/agent/capabilities\", // Agent-paradigm discovery manifest") {
t.Error("router.go publicPaths must include /api/agent/capabilities so the discovery manifest serves without a token — agents need to introspect Pulse before they have credentials")
}
}
// TestContract_GetFleetContextCapabilityListed pins the discovery
// contract: the capabilities manifest must declare get_fleet_context
// so an agent reading the manifest learns the fleet view exists and
// what its response shape is. Drift here means external agents
// still expect to walk every resource id one-by-one.
func TestContract_GetFleetContextCapabilityListed(t *testing.T) {
var cap *AgentCapability
manifest := agentcapabilities.CanonicalManifest()
for i := range manifest.Capabilities {
if manifest.Capabilities[i].Name == "get_fleet_context" {
cap = &manifest.Capabilities[i]
break
}
}
if cap == nil {
t.Fatal("agent capabilities manifest must declare get_fleet_context — the substrate's triage entry point")
}
if cap.Path != agentcapabilities.FleetContextCapabilityPath {
t.Errorf("get_fleet_context path = %q, want %s", cap.Path, agentcapabilities.FleetContextCapabilityPath)
}
if cap.ResponseShape != "AgentFleetContext" {
t.Errorf("get_fleet_context response shape = %q, want AgentFleetContext so agents know what to parse", cap.ResponseShape)
}
}
// TestContract_FindingsResourceOperatorStateProviderIsWired pins the
// startup wiring that gives the findings runtime access to
// per-resource operator-set state via the API layer's adapter. The
// router must call SetResourceOperatorStateProvider with a closure
// that reads the unified store's GetResourceOperatorState and
// projects IsInMaintenanceAt into the ActiveMaintenanceWindow shape;
// regressions here would silently disable maintenance-window
// auto-acknowledgement without breaking any unit tests because the
// behavior is opt-in.
func TestContract_FindingsResourceOperatorStateProviderIsWired(t *testing.T) {
source, err := os.ReadFile("router.go")
if err != nil {
t.Fatalf("read router.go: %v", err)
}
src := string(source)
if !strings.Contains(src, "SetResourceOperatorStateProvider(") {
t.Error("router.go must call SetResourceOperatorStateProvider on the findings store at startup")
}
if !strings.Contains(src, "SetResourceStoreProvider(r.resourceHandlers.getStore)") {
t.Error("router.go must expose the resource store to the Patrol findings handler so read-time priority changes surface immediately")
}
if !strings.Contains(src, "ResourceOperatorStateProviderFunc(") {
t.Error("router.go must use the ai.ResourceOperatorStateProviderFunc adapter so the closure satisfies the provider interface")
}
if !strings.Contains(src, "IsInMaintenanceAt(now)") {
t.Error("router.go must gate the projection on state.IsInMaintenanceAt so a stale window does not return as active")
}
if !strings.Contains(src, "GetResourceOperatorState(canonicalID)") {
t.Error("router.go must read state from the unified store's canonical accessor")
}
if !strings.Contains(src, "IntentionallyOffline: state.IntentionallyOffline") {
t.Error("router.go must propagate state.IntentionallyOffline through the projection so the findings runtime sees it")
}
if !strings.Contains(src, "Criticality: string(state.Criticality)") {
t.Error("router.go must propagate state.Criticality through the projection so Patrol attention ordering sees it")
}
}
// TestContract_ResourceOperatorStateUrlCanonicalIDWinsOverBody pins the
// security-relevant decision that the URL path's canonical_id always
// wins over any body-supplied value on PUT
// /api/resources/{id}/operator-state. Without this rule a request
// authorized to write at /vm:101 could retarget the write at /vm:102
// through body manipulation, defeating per-resource scoping.
func TestContract_ResourceOperatorStateUrlCanonicalIDWinsOverBody(t *testing.T) {
cfg := &config.Config{DataPath: t.TempDir()}
h := NewResourceHandlers(cfg)
body := []byte(`{"canonicalId":"vm:999","intentionallyOffline":true}`)
rec := httptest.NewRecorder()
req := httptest.NewRequest(http.MethodPut, "/api/resources/vm:101/operator-state", bytes.NewReader(body))
h.HandleResourceOperatorState(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("expected 200; got %d body=%s", rec.Code, rec.Body.String())
}
var persisted resourceOperatorStateAPI
if err := json.Unmarshal(rec.Body.Bytes(), &persisted); err != nil {
t.Fatalf("response not JSON: %v", err)
}
if persisted.CanonicalID != "vm:101" {
t.Errorf("URL canonical_id must override body; got %q (security regression)", persisted.CanonicalID)
}
// And the resource named in the body must NOT have a state row,
// confirming the write didn't bleed across resource scope.
rec2 := httptest.NewRecorder()
req2 := httptest.NewRequest(http.MethodGet, "/api/resources/vm:999/operator-state", nil)
h.HandleResourceOperatorState(rec2, req2)
if rec2.Code != http.StatusNotFound {
t.Errorf("body-supplied canonical_id must not have been persisted; GET vm:999 returned %d", rec2.Code)
}
}
// TestContract_ProxmoxGuestDockerDetectionRequiresExplicitOptIn pins the
// privacy boundary for Proxmox-side LXC Docker socket hinting and inventory.
// Router startup may wire a Docker checker or collector only when the server
// has the explicit opt-in config.
func TestContract_ProxmoxGuestDockerDetectionRequiresExplicitOptIn(t *testing.T) {
t.Run("disabled without explicit opt-in", func(t *testing.T) {
monitor := &monitoring.Monitor{}
router := &Router{
config: &config.Config{
EnableProxmoxGuestDockerDetection: false,
},
agentExecServer: agentexec.NewServer(func(token string, agentID string, hostname string) bool {
return false
}),
}
router.configureProxmoxGuestDockerDetection(monitor)
if got := monitor.GetDockerChecker(); got != nil {
t.Fatalf("expected Proxmox guest Docker checker to stay disabled without explicit opt-in, got %T", got)
}
if got := monitor.GetDockerInventoryCollector(); got != nil {
t.Fatalf("expected Proxmox guest Docker inventory collector to stay disabled without explicit opt-in, got %T", got)
}
})
t.Run("socket hinting enabled only with explicit opt-in", func(t *testing.T) {
monitor := &monitoring.Monitor{}
router := &Router{
config: &config.Config{
EnableProxmoxGuestDockerDetection: true,
},
agentExecServer: agentexec.NewServer(func(token string, agentID string, hostname string) bool {
return false
}),
}
router.configureProxmoxGuestDockerDetection(monitor)
if got := monitor.GetDockerChecker(); got == nil {
t.Fatal("expected Proxmox guest Docker checker after explicit opt-in")
}
if got := monitor.GetDockerInventoryCollector(); got != nil {
t.Fatalf("expected Proxmox guest Docker inventory collector to stay disabled without inventory opt-in, got %T", got)
}
})
t.Run("inventory opt-in wires checker and collector", func(t *testing.T) {
monitor := &monitoring.Monitor{}
router := &Router{
config: &config.Config{
EnableProxmoxGuestDockerInventory: true,
},
agentExecServer: agentexec.NewServer(func(token string, agentID string, hostname string) bool {
return false
}),
}
router.configureProxmoxGuestDockerDetection(monitor)
if got := monitor.GetDockerChecker(); got == nil {
t.Fatal("expected Proxmox guest Docker checker after inventory opt-in")
}
if got := monitor.GetDockerInventoryCollector(); got == nil {
t.Fatal("expected Proxmox guest Docker inventory collector after inventory opt-in")
}
})
}
func TestContract_DockerPodmanAdminCopyUsesPulseAgentModuleIdentity(t *testing.T) {
files := []string{
"diagnostics.go",
"docker_agents.go",
"types.go",
"update_detection.go",
}
for _, file := range files {
t.Run(file, func(t *testing.T) {
src, err := os.ReadFile(file)
if err != nil {
t.Fatalf("read %s: %v", file, err)
}
text := string(src)
for _, forbidden := range []string{
"Docker" + " agent",
"docker" + " agent",
"Docker / Podman" + " agent",
"Docker / Podman" + " agents",
} {
if strings.Contains(text, forbidden) {
t.Fatalf("%s must describe Docker / Podman as a pulse-agent module, not %q", file, forbidden)
}
}
})
}
diagnostics, err := os.ReadFile("diagnostics.go")
if err != nil {
t.Fatalf("read diagnostics.go: %v", err)
}
for _, required := range []string{
"Docker / Podman module is still using the shared API token",
"No Docker / Podman modules have reported in yet",
"All Docker / Podman modules are reporting with dedicated tokens and the expected version.",
} {
if !strings.Contains(string(diagnostics), required) {
t.Errorf("diagnostics.go must preserve module copy %q", required)
}
}
}
func TestContract_UpdateReadinessIncludesV5AgentMigrationSecurityGuidance(t *testing.T) {
source, err := os.ReadFile("update_readiness.go")
if err != nil {
t.Fatalf("read update_readiness.go: %v", err)
}
text := string(source)
for _, required := range []string{
`ID: "agent-migration-security"`,
"v5 agents can auto-update to v6, but the first hop depends on trusted transport.",
"Use HTTPS, or keep the Pulse-to-agent migration path on a trusted local network",
"For high-assurance environments, reinstall the v6 pulse-agent through the signed installer path",
} {
if !strings.Contains(text, required) {
t.Errorf("update_readiness.go must preserve v5-to-v6 migration security guidance %q", required)
}
}
}
func TestContract_MetadataGetPayloadsUseZeroRecordsInsteadOf404(t *testing.T) {
mtp := config.NewMultiTenantPersistence(t.TempDir())
guestHandler := NewGuestMetadataHandler(mtp)
dockerHandler := NewDockerMetadataHandler(mtp)
// An empty store must serialize as an empty JSON object, never null.
rec := httptest.NewRecorder()
guestHandler.HandleGetMetadata(rec, httptest.NewRequest(http.MethodGet, "/api/guests/metadata", nil))
if body := strings.TrimSpace(rec.Body.String()); body != "{}" {
t.Fatalf("empty guest metadata map must serialize as {}, got %q", body)
}
rec = httptest.NewRecorder()
dockerHandler.HandleGetMetadata(rec, httptest.NewRequest(http.MethodGet, "/api/docker/metadata", nil))
if body := strings.TrimSpace(rec.Body.String()); body != "{}" {
t.Fatalf("empty docker metadata map must serialize as {}, got %q", body)
}
// A missing resource must return a zero record carrying the requested ID,
// never a 404 — clients rely on this to render empty metadata forms.
rec = httptest.NewRecorder()
guestHandler.HandleGetMetadata(rec, httptest.NewRequest(http.MethodGet, "/api/guests/metadata/qemu-100", nil))
if rec.Code != http.StatusOK {
t.Fatalf("missing guest metadata must respond 200, got %d", rec.Code)
}
var guestMeta config.GuestMetadata
if err := json.Unmarshal(rec.Body.Bytes(), &guestMeta); err != nil {
t.Fatalf("decode guest metadata response: %v", err)
}
if guestMeta.ID != "qemu-100" {
t.Fatalf("missing guest metadata must echo the requested ID, got %q", guestMeta.ID)
}
rec = httptest.NewRecorder()
dockerHandler.HandleGetMetadata(rec, httptest.NewRequest(http.MethodGet, "/api/docker/metadata/host-1", nil))
if rec.Code != http.StatusOK {
t.Fatalf("missing docker metadata must respond 200, got %d", rec.Code)
}
var dockerMeta config.DockerMetadata
if err := json.Unmarshal(rec.Body.Bytes(), &dockerMeta); err != nil {
t.Fatalf("decode docker metadata response: %v", err)
}
if dockerMeta.ID != "host-1" {
t.Fatalf("missing docker metadata must echo the requested ID, got %q", dockerMeta.ID)
}
}
// TestContract_WebhookSigningSecretMaskedAndPreserved pins the webhook
// management payload contract for delivery signing secrets: the list API must
// mask a configured secret with the shared redaction placeholder, and an
// update that echoes the placeholder must preserve the stored secret instead
// of overwriting it with the literal placeholder string.
func TestContract_WebhookSigningSecretMaskedAndPreserved(t *testing.T) {
mockMonitor := new(MockNotificationMonitor)
mockManager := new(MockNotificationManager)
mockPersistence := new(MockNotificationConfigPersistence)
mockMonitor.On("GetNotificationManager").Return(mockManager)
mockMonitor.On("GetConfigPersistence").Return(mockPersistence)
h := NewNotificationHandlers(nil, mockMonitor)
stored := notifications.WebhookConfig{
ID: "wh-signed",
Name: "Signed Webhook",
URL: "https://psa.example.com/inbound/pulse",
Enabled: true,
Service: "generic",
SigningSecret: "stored-secret",
}
// List must mask the configured secret.
mockManager.On("GetWebhooks").Return([]notifications.WebhookConfig{stored}).Once()
rec := httptest.NewRecorder()
h.GetWebhooks(rec, httptest.NewRequest(http.MethodGet, "/api/notifications/webhooks", nil))
if rec.Code != http.StatusOK {
t.Fatalf("GetWebhooks responded %d", rec.Code)
}
var listed []map[string]interface{}
if err := json.Unmarshal(rec.Body.Bytes(), &listed); err != nil {
t.Fatalf("decode webhook list: %v", err)
}
if len(listed) != 1 {
t.Fatalf("expected one webhook in list, got %d", len(listed))
}
if got := listed[0]["signingSecret"]; got != "***REDACTED***" {
t.Fatalf("list signingSecret = %v, want masked placeholder", got)
}
// Update echoing the masked placeholder must preserve the stored secret.
mockManager.On("GetWebhooks").Return([]notifications.WebhookConfig{stored}).Once()
mockManager.On("ValidateWebhookURL", stored.URL).Return(nil).Once()
mockManager.On("UpdateWebhook", "wh-signed", tmock.MatchedBy(func(w notifications.WebhookConfig) bool {
return w.SigningSecret == "stored-secret"
})).Return(nil).Once()
mockManager.On("GetWebhooks").Return([]notifications.WebhookConfig{stored}).Once()
mockPersistence.On("SaveWebhooks", tmock.Anything).Return(nil).Once()
update := stored
update.SigningSecret = "***REDACTED***"
body, err := json.Marshal(update)
if err != nil {
t.Fatalf("marshal update: %v", err)
}
rec = httptest.NewRecorder()
h.UpdateWebhook(rec, httptest.NewRequest(http.MethodPut, "/api/notifications/webhooks/wh-signed", bytes.NewReader(body)))
if rec.Code != http.StatusOK {
t.Fatalf("UpdateWebhook responded %d: %s", rec.Code, rec.Body.String())
}
mockManager.AssertExpectations(t)
}
// TestContract_OrgBoundTokenIsScopedAwayFromDefaultOrg pins the MSP tenant
// isolation boundary: a token explicitly bound to client organizations must
// not implicitly reach the default org's data (a leaked client-site token
// must not read the provider's own estate), while users and legacy unbound
// tokens retain default-org access for backward compatibility.
func TestContract_OrgBoundTokenIsScopedAwayFromDefaultOrg(t *testing.T) {
checker := NewAuthorizationChecker(nil)
if res := checker.CheckAccess(&config.APITokenRecord{OrgID: "client-acme"}, "", "default"); res.Allowed {
t.Fatal("org-bound token must not implicitly access the default org")
}
if res := checker.CheckAccess(&config.APITokenRecord{OrgIDs: []string{"client-a", "client-b"}}, "", "default"); res.Allowed {
t.Fatal("multi-org-bound token must not implicitly access the default org")
}
if res := checker.CheckAccess(&config.APITokenRecord{OrgID: "default"}, "", "default"); !res.Allowed {
t.Fatal("token explicitly bound to default must access the default org")
}
if res := checker.CheckAccess(&config.APITokenRecord{}, "", "default"); !res.Allowed {
t.Fatal("legacy unbound token must retain default-org access")
}
if res := checker.CheckAccess(nil, "admin", "default"); !res.Allowed {
t.Fatal("authenticated users must retain default-org access")
}
}
// TestContract_ProxyAuthConfiguredRoleHeaderMissingIsNonAdmin pins the
// proxy-auth admin boundary: once an admin role header is configured, admin
// authorization must be proven by that header rather than inherited from
// successful proxy authentication alone.
func TestContract_ProxyAuthConfiguredRoleHeaderMissingIsNonAdmin(t *testing.T) {
cfg := &config.Config{
ProxyAuthSecret: "secret123",
ProxyAuthUserHeader: "X-Remote-User",
ProxyAuthRoleHeader: "X-Remote-Roles",
ProxyAuthAdminRole: "admin",
}
handlerCalled := false
handler := RequireAdmin(cfg, func(w http.ResponseWriter, r *http.Request) {
handlerCalled = true
w.WriteHeader(http.StatusOK)
})
req := httptest.NewRequest(http.MethodGet, "/api/admin/test", nil)
req.Header.Set("X-Proxy-Secret", "secret123")
req.Header.Set("X-Remote-User", "regular-user")
rec := httptest.NewRecorder()
handler(rec, req)
if handlerCalled {
t.Fatal("admin handler was called without the configured role header")
}
if rec.Code != http.StatusForbidden {
t.Fatalf("status = %d, want %d", rec.Code, http.StatusForbidden)
}
}