From 6b2d1375e6065b82ba9dd42efd8c982cc5e4aa1e Mon Sep 17 00:00:00 2001 From: Shankar Date: Sat, 18 Apr 2026 01:25:20 +0000 Subject: [PATCH] fix(m2-pr-e): collapse AgentService.HeartbeatWithContext into Heartbeat MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PR-E of 6 in the M-2 end-to-end remediation sequence. Collapses the HeartbeatWithContext wrapper into a single ctx-first Heartbeat method, matching D-1 (ctx-only signatures, no dual forms). The handler-facing method name is preserved (D-4) — internal/api/handler/agents.go already declares `Heartbeat(ctx, ...)` on its local service interface, and the handler mock at internal/api/handler/agent_handler_test.go already takes `_ context.Context` as its first param, so no handler churn. Changes ------- internal/service/agent.go - Delete the zero-body Heartbeat wrapper that forwarded to HeartbeatWithContext with context.Background(). - Rename HeartbeatWithContext → Heartbeat (ctx-bearing body folded directly into the canonical method). internal/service/agent_test.go - TestHeartbeat (L95) and TestHeartbeat_NotFound (L128): agentService.HeartbeatWithContext(ctx, ...) → .Heartbeat(ctx, ...). internal/service/concurrent_test.go - L162: agentSvc.HeartbeatWithContext(ctx, agentID, metadata) → .Heartbeat(ctx, agentID, metadata). internal/service/context_test.go - L179 + L232: agentSvc.HeartbeatWithContext(ctx, ...) → .Heartbeat(...) - L185 + L238 t.Logf strings: "HeartbeatWithContext with ..." → "Heartbeat with ..." to match the collapsed method name. Verification (Go 1.25.9 linux/arm64, CI-parity caches) ------------------------------------------------------ go build ./... clean go vet ./... clean go test -short ./internal/service/... ./internal/api/handler/... \ ./internal/integration/... all ok go test -race -short same set all ok go test -short ./... all packages ok golangci-lint run ./... 0 issues Locked decisions from the M-2 plan: D-1 ctx-only signatures (no dual forms) D-4 preserve handler method names facing the router D-5 domain types stay ctx-free Audit complete. Commit: 855124a9d9c784da85c6467ac56afa5931e25106. Sections: 12. Findings: 2/7/10/4/6. --- internal/service/agent.go | 10 ++-------- internal/service/agent_test.go | 4 ++-- internal/service/concurrent_test.go | 2 +- internal/service/context_test.go | 8 ++++---- 4 files changed, 9 insertions(+), 15 deletions(-) diff --git a/internal/service/agent.go b/internal/service/agent.go index 1a14718..00ba028 100644 --- a/internal/service/agent.go +++ b/internal/service/agent.go @@ -91,8 +91,8 @@ func (s *AgentService) Register(ctx context.Context, name string, hostname strin return agent, apiKey, nil } -// HeartbeatWithContext updates an agent's last seen time, status, and metadata. -func (s *AgentService) HeartbeatWithContext(ctx context.Context, agentID string, metadata *domain.AgentMetadata) error { +// Heartbeat updates an agent's last seen time, status, and metadata. +func (s *AgentService) Heartbeat(ctx context.Context, agentID string, metadata *domain.AgentMetadata) error { agent, err := s.agentRepo.Get(ctx, agentID) if err != nil { return fmt.Errorf("failed to fetch agent: %w", err) @@ -114,12 +114,6 @@ func (s *AgentService) HeartbeatWithContext(ctx context.Context, agentID string, return nil } -// Heartbeat updates agent heartbeat (handler interface method). -// Note: This method is called from handlers which have a context; callers should prefer HeartbeatWithContext. -func (s *AgentService) Heartbeat(ctx context.Context, agentID string, metadata *domain.AgentMetadata) error { - return s.HeartbeatWithContext(ctx, agentID, metadata) -} - // SubmitCSR validates and processes a Certificate Signing Request from an agent. // In agent keygen mode, this completes an AwaitingCSR renewal job by signing the CSR // and storing the cert version. The private key stays on the agent — only the CSR diff --git a/internal/service/agent_test.go b/internal/service/agent_test.go index 4d1a6b9..7271feb 100644 --- a/internal/service/agent_test.go +++ b/internal/service/agent_test.go @@ -92,7 +92,7 @@ func TestHeartbeat(t *testing.T) { agentService := NewAgentService(agentRepo, certRepo, jobRepo, targetRepo, auditService, issuerRegistry, nil) - err := agentService.HeartbeatWithContext(ctx, "agent-001", nil) + err := agentService.Heartbeat(ctx, "agent-001", nil) if err != nil { t.Fatalf("Heartbeat failed: %v", err) } @@ -125,7 +125,7 @@ func TestHeartbeat_NotFound(t *testing.T) { agentService := NewAgentService(agentRepo, certRepo, jobRepo, targetRepo, auditService, issuerRegistry, nil) - err := agentService.HeartbeatWithContext(ctx, "nonexistent", nil) + err := agentService.Heartbeat(ctx, "nonexistent", nil) if err == nil { t.Fatal("expected error for nonexistent agent") } diff --git a/internal/service/concurrent_test.go b/internal/service/concurrent_test.go index 9df68c5..ccb4d83 100644 --- a/internal/service/concurrent_test.go +++ b/internal/service/concurrent_test.go @@ -159,7 +159,7 @@ func TestConcurrentAgentHeartbeats(t *testing.T) { Architecture: "x86_64", } - err := agentSvc.HeartbeatWithContext(ctx, agentID, metadata) + err := agentSvc.Heartbeat(ctx, agentID, metadata) if err != nil { errChan <- fmt.Errorf("goroutine %d: failed heartbeat for agent %s: %w", idx, agentID, err) return diff --git a/internal/service/context_test.go b/internal/service/context_test.go index 440c590..a7f0b72 100644 --- a/internal/service/context_test.go +++ b/internal/service/context_test.go @@ -176,13 +176,13 @@ func TestAgentService_HeartbeatWithCancelledContext(t *testing.T) { nil, // renewalService ) - err := agentSvc.HeartbeatWithContext(ctx, "agent-1", &domain.AgentMetadata{}) + err := agentSvc.Heartbeat(ctx, "agent-1", &domain.AgentMetadata{}) // Service should handle cancelled context if err == nil || ctx.Err() == context.Canceled { return } - t.Logf("HeartbeatWithContext with cancelled context returned: %v", err) + t.Logf("Heartbeat with cancelled context returned: %v", err) } // Test with timeout context (should trigger deadline exceeded) @@ -229,11 +229,11 @@ func TestAgentService_HeartbeatWithDeadlineExceeded(t *testing.T) { time.Sleep(10 * time.Millisecond) // Ensure deadline is exceeded - err := agentSvc.HeartbeatWithContext(ctx, "agent-1", &domain.AgentMetadata{}) + err := agentSvc.Heartbeat(ctx, "agent-1", &domain.AgentMetadata{}) // Service should handle deadline exceeded if err == nil || ctx.Err() == context.DeadlineExceeded { return } - t.Logf("HeartbeatWithContext with deadline exceeded returned: %v", err) + t.Logf("Heartbeat with deadline exceeded returned: %v", err) }