refactor: rename agent group→application across entire codebase
All checks were successful
CI / build (push) Successful in 1m22s
CI / cleanup-branch (push) Has been skipped
CI / docker (push) Successful in 52s
CI / deploy (push) Successful in 39s
CI / deploy-feature (push) Has been skipped

Complete the group→application terminology rename in the agent
registry subsystem:

- AgentInfo: field group → application, all wither methods updated
- AgentRegistryService: findByGroup → findByApplication
- AgentInstanceResponse: field group → application (API response)
- AgentRegistrationRequest: field group → application (API request)
- JwtServiceImpl: parameter names group → application (JWT claim
  string "group" preserved for token backward compatibility)
- All controllers, lifecycle monitor, command controller updated
- Integration tests: JSON request bodies "group" → "application"
- Frontend: schema.d.ts, openapi.json, agent queries, AgentHealth

RBAC group references (groups table, GroupAdminController, etc.)
are NOT affected — they are a separate domain concept.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
hsiegeln
2026-03-24 08:48:12 +01:00
parent 413839452c
commit ff76751629
28 changed files with 104 additions and 4618 deletions

View File

@@ -51,7 +51,7 @@ public class AgentLifecycleMonitor {
if (before != null && before != agent.state()) {
String eventType = mapTransitionEvent(before, agent.state());
if (eventType != null) {
agentEventService.recordEvent(agent.id(), agent.group(), eventType,
agentEventService.recordEvent(agent.id(), agent.application(), eventType,
agent.name() + " " + before + " -> " + agent.state());
}
}

View File

@@ -92,7 +92,7 @@ public class AgentCommandController {
List<AgentInfo> agents = registryService.findAll().stream()
.filter(a -> a.state() == AgentState.LIVE)
.filter(a -> group.equals(a.group()))
.filter(a -> group.equals(a.application()))
.toList();
List<String> commandIds = new ArrayList<>();

View File

@@ -102,21 +102,21 @@ public class AgentRegistrationController {
return ResponseEntity.badRequest().build();
}
String group = request.group() != null ? request.group() : "default";
String application = request.application() != null ? request.application() : "default";
List<String> routeIds = request.routeIds() != null ? request.routeIds() : List.of();
var capabilities = request.capabilities() != null ? request.capabilities() : Collections.<String, Object>emptyMap();
AgentInfo agent = registryService.register(
request.agentId(), request.name(), group, request.version(), routeIds, capabilities);
log.info("Agent registered: {} (name={}, group={})", request.agentId(), request.name(), group);
request.agentId(), request.name(), application, request.version(), routeIds, capabilities);
log.info("Agent registered: {} (name={}, application={})", request.agentId(), request.name(), application);
agentEventService.recordEvent(request.agentId(), group, "REGISTERED",
agentEventService.recordEvent(request.agentId(), application, "REGISTERED",
"Agent registered: " + request.name());
// Issue JWT tokens with AGENT role
List<String> roles = List.of("AGENT");
String accessToken = jwtService.createAccessToken(request.agentId(), group, roles);
String refreshToken = jwtService.createRefreshToken(request.agentId(), group, roles);
String accessToken = jwtService.createAccessToken(request.agentId(), application, roles);
String refreshToken = jwtService.createRefreshToken(request.agentId(), application, roles);
return ResponseEntity.ok(new AgentRegistrationResponse(
agent.id(),
@@ -166,8 +166,8 @@ public class AgentRegistrationController {
// Preserve roles from refresh token
List<String> roles = result.roles().isEmpty()
? List.of("AGENT") : result.roles();
String newAccessToken = jwtService.createAccessToken(agentId, agent.group(), roles);
String newRefreshToken = jwtService.createRefreshToken(agentId, agent.group(), roles);
String newAccessToken = jwtService.createAccessToken(agentId, agent.application(), roles);
String newRefreshToken = jwtService.createRefreshToken(agentId, agent.application(), roles);
return ResponseEntity.ok(new AgentRefreshResponse(newAccessToken, newRefreshToken));
}
@@ -187,13 +187,13 @@ public class AgentRegistrationController {
@GetMapping
@Operation(summary = "List all agents",
description = "Returns all registered agents with runtime metrics, optionally filtered by status and/or group")
description = "Returns all registered agents with runtime metrics, optionally filtered by status and/or application")
@ApiResponse(responseCode = "200", description = "Agent list returned")
@ApiResponse(responseCode = "400", description = "Invalid status filter",
content = @Content(schema = @Schema(implementation = ErrorResponse.class)))
public ResponseEntity<List<AgentInstanceResponse>> listAgents(
@RequestParam(required = false) String status,
@RequestParam(required = false) String group) {
@RequestParam(required = false) String application) {
List<AgentInfo> agents;
if (status != null) {
@@ -207,10 +207,10 @@ public class AgentRegistrationController {
agents = registryService.findAll();
}
// Apply group filter if specified
if (group != null && !group.isBlank()) {
// Apply application filter if specified
if (application != null && !application.isBlank()) {
agents = agents.stream()
.filter(a -> group.equals(a.group()))
.filter(a -> application.equals(a.application()))
.toList();
}
@@ -221,11 +221,11 @@ public class AgentRegistrationController {
List<AgentInstanceResponse> response = finalAgents.stream()
.map(a -> {
AgentInstanceResponse dto = AgentInstanceResponse.from(a);
double[] m = agentMetrics.get(a.group());
double[] m = agentMetrics.get(a.application());
if (m != null) {
long groupAgentCount = finalAgents.stream()
.filter(ag -> ag.group().equals(a.group())).count();
double agentTps = groupAgentCount > 0 ? m[0] / groupAgentCount : 0;
long appAgentCount = finalAgents.stream()
.filter(ag -> ag.application().equals(a.application())).count();
double agentTps = appAgentCount > 0 ? m[0] / appAgentCount : 0;
double errorRate = m[1];
int activeRoutes = (int) m[2];
return dto.withMetrics(agentTps, errorRate, activeRoutes);

View File

@@ -90,14 +90,14 @@ public class DiagramRenderController {
}
@GetMapping
@Operation(summary = "Find diagram by application group and route ID",
description = "Resolves group to agent IDs and finds the latest diagram for the route")
@Operation(summary = "Find diagram by application and route ID",
description = "Resolves application to agent IDs and finds the latest diagram for the route")
@ApiResponse(responseCode = "200", description = "Diagram layout returned")
@ApiResponse(responseCode = "404", description = "No diagram found for the given group and route")
public ResponseEntity<DiagramLayout> findByGroupAndRoute(
@RequestParam String group,
@ApiResponse(responseCode = "404", description = "No diagram found for the given application and route")
public ResponseEntity<DiagramLayout> findByApplicationAndRoute(
@RequestParam String application,
@RequestParam String routeId) {
List<String> agentIds = registryService.findByGroup(group).stream()
List<String> agentIds = registryService.findByApplication(application).stream()
.map(AgentInfo::id)
.toList();

View File

@@ -70,7 +70,7 @@ public class ExecutionController {
private String resolveApplicationName(String agentId) {
AgentInfo agent = registryService.findById(agentId);
return agent != null ? agent.group() : "";
return agent != null ? agent.application() : "";
}
private List<RouteExecution> parsePayload(String body) throws JsonProcessingException {

View File

@@ -47,9 +47,9 @@ public class RouteCatalogController {
public ResponseEntity<List<AppCatalogEntry>> getCatalog() {
List<AgentInfo> allAgents = registryService.findAll();
// Group agents by application (group name)
// Group agents by application name
Map<String, List<AgentInfo>> agentsByApp = allAgents.stream()
.collect(Collectors.groupingBy(AgentInfo::group, LinkedHashMap::new, Collectors.toList()));
.collect(Collectors.groupingBy(AgentInfo::application, LinkedHashMap::new, Collectors.toList()));
// Collect all distinct routes per app
Map<String, Set<String>> routesByApp = new LinkedHashMap<>();

View File

@@ -134,7 +134,7 @@ public class SearchController {
if (application == null || application.isBlank()) {
return null;
}
return registryService.findByGroup(application).stream()
return registryService.findByApplication(application).stream()
.map(AgentInfo::id)
.toList();
}

View File

@@ -13,7 +13,7 @@ import java.util.Map;
public record AgentInstanceResponse(
@NotNull String id,
@NotNull String name,
@NotNull String group,
@NotNull String application,
@NotNull String status,
@NotNull List<String> routeIds,
@NotNull Instant registeredAt,
@@ -29,7 +29,7 @@ public record AgentInstanceResponse(
public static AgentInstanceResponse from(AgentInfo info) {
long uptime = Duration.between(info.registeredAt(), Instant.now()).toSeconds();
return new AgentInstanceResponse(
info.id(), info.name(), info.group(),
info.id(), info.name(), info.application(),
info.state().name(), info.routeIds(),
info.registeredAt(), info.lastHeartbeat(),
info.version(), info.capabilities(),
@@ -41,7 +41,7 @@ public record AgentInstanceResponse(
public AgentInstanceResponse withMetrics(double tps, double errorRate, int activeRoutes) {
return new AgentInstanceResponse(
id, name, group, status, routeIds, registeredAt, lastHeartbeat,
id, name, application, status, routeIds, registeredAt, lastHeartbeat,
version, capabilities,
tps, errorRate, activeRoutes, totalRoutes, uptimeSeconds
);

View File

@@ -10,7 +10,7 @@ import java.util.Map;
public record AgentRegistrationRequest(
@NotNull String agentId,
@NotNull String name,
@Schema(defaultValue = "default") String group,
@Schema(defaultValue = "default") String application,
String version,
List<String> routeIds,
Map<String, Object> capabilities

View File

@@ -60,13 +60,13 @@ public class JwtServiceImpl implements JwtService {
}
@Override
public String createAccessToken(String subject, String group, List<String> roles) {
return createToken(subject, group, roles, "access", properties.getAccessTokenExpiryMs());
public String createAccessToken(String subject, String application, List<String> roles) {
return createToken(subject, application, roles, "access", properties.getAccessTokenExpiryMs());
}
@Override
public String createRefreshToken(String subject, String group, List<String> roles) {
return createToken(subject, group, roles, "refresh", properties.getRefreshTokenExpiryMs());
public String createRefreshToken(String subject, String application, List<String> roles) {
return createToken(subject, application, roles, "refresh", properties.getRefreshTokenExpiryMs());
}
@Override
@@ -84,12 +84,12 @@ public class JwtServiceImpl implements JwtService {
return validateAccessToken(token).subject();
}
private String createToken(String subject, String group, List<String> roles,
private String createToken(String subject, String application, List<String> roles,
String type, long expiryMs) {
Instant now = Instant.now();
JWTClaimsSet claims = new JWTClaimsSet.Builder()
.subject(subject)
.claim("group", group)
.claim("group", application)
.claim("type", type)
.claim("roles", roles)
.issueTime(Date.from(now))
@@ -132,7 +132,7 @@ public class JwtServiceImpl implements JwtService {
throw new InvalidTokenException("Token has no subject");
}
String group = claims.getStringClaim("group");
String application = claims.getStringClaim("group");
// Extract roles — may be absent in legacy tokens
List<String> roles;
@@ -145,7 +145,7 @@ public class JwtServiceImpl implements JwtService {
roles = List.of();
}
return new JwtValidationResult(subject, group, roles);
return new JwtValidationResult(subject, application, roles);
} catch (ParseException e) {
throw new InvalidTokenException("Failed to parse JWT", e);
} catch (JOSEException e) {

View File

@@ -37,8 +37,8 @@ public class TestSecurityHelper {
/**
* Returns a valid JWT access token with the given roles (no agent registration).
*/
public String createToken(String subject, String group, List<String> roles) {
return jwtService.createAccessToken(subject, group, roles);
public String createToken(String subject, String application, List<String> roles) {
return jwtService.createAccessToken(subject, application, roles);
}
/**

View File

@@ -38,17 +38,17 @@ class AgentCommandControllerIT extends AbstractPostgresIT {
operatorJwt = securityHelper.operatorToken();
}
private ResponseEntity<String> registerAgent(String agentId, String name, String group) {
private ResponseEntity<String> registerAgent(String agentId, String name, String application) {
String json = """
{
"agentId": "%s",
"name": "%s",
"group": "%s",
"application": "%s",
"version": "1.0.0",
"routeIds": ["route-1"],
"capabilities": {}
}
""".formatted(agentId, name, group);
""".formatted(agentId, name, application);
return restTemplate.postForEntity(
"/api/v1/agents/register",

View File

@@ -41,7 +41,7 @@ class AgentRegistrationControllerIT extends AbstractPostgresIT {
{
"agentId": "%s",
"name": "%s",
"group": "test-group",
"application": "test-group",
"version": "1.0.0",
"routeIds": ["route-1", "route-2"],
"capabilities": {"tracing": true}

View File

@@ -53,17 +53,17 @@ class AgentSseControllerIT extends AbstractPostgresIT {
operatorJwt = securityHelper.operatorToken();
}
private ResponseEntity<String> registerAgent(String agentId, String name, String group) {
private ResponseEntity<String> registerAgent(String agentId, String name, String application) {
String json = """
{
"agentId": "%s",
"name": "%s",
"group": "%s",
"application": "%s",
"version": "1.0.0",
"routeIds": ["route-1"],
"capabilities": {}
}
""".formatted(agentId, name, group);
""".formatted(agentId, name, application);
return restTemplate.postForEntity(
"/api/v1/agents/register",

View File

@@ -29,7 +29,7 @@ class BootstrapTokenIT extends AbstractPostgresIT {
{
"agentId": "bootstrap-test-agent",
"name": "Bootstrap Test",
"group": "test-group",
"application": "test-group",
"version": "1.0.0",
"routeIds": [],
"capabilities": {}
@@ -97,7 +97,7 @@ class BootstrapTokenIT extends AbstractPostgresIT {
{
"agentId": "bootstrap-test-previous",
"name": "Previous Token Test",
"group": "test-group",
"application": "test-group",
"version": "1.0.0",
"routeIds": [],
"capabilities": {}

View File

@@ -39,7 +39,7 @@ class JwtRefreshIT extends AbstractPostgresIT {
{
"agentId": "%s",
"name": "Refresh Test Agent",
"group": "test-group",
"application": "test-group",
"version": "1.0.0",
"routeIds": [],
"capabilities": {}

View File

@@ -78,7 +78,7 @@ class JwtServiceTest {
String token = jwtService.createAccessToken("user:admin", "user", roles);
JwtService.JwtValidationResult result = jwtService.validateAccessToken(token);
assertEquals("user:admin", result.subject());
assertEquals("user", result.group());
assertEquals("user", result.application());
assertEquals(roles, result.roles());
}
@@ -88,7 +88,7 @@ class JwtServiceTest {
String token = jwtService.createRefreshToken("agent-1", "default", roles);
JwtService.JwtValidationResult result = jwtService.validateRefreshToken(token);
assertEquals("agent-1", result.subject());
assertEquals("default", result.group());
assertEquals("default", result.application());
assertEquals(roles, result.roles());
}

View File

@@ -32,7 +32,7 @@ class RegistrationSecurityIT extends AbstractPostgresIT {
{
"agentId": "%s",
"name": "Security Test Agent",
"group": "test-group",
"application": "test-group",
"version": "1.0.0",
"routeIds": [],
"capabilities": {}

View File

@@ -90,7 +90,7 @@ class SseSigningIT extends AbstractPostgresIT {
{
"agentId": "%s",
"name": "SSE Signing Test Agent",
"group": "test-group",
"application": "test-group",
"version": "1.0.0",
"routeIds": ["route-1"],
"capabilities": {}