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()) { if (before != null && before != agent.state()) {
String eventType = mapTransitionEvent(before, agent.state()); String eventType = mapTransitionEvent(before, agent.state());
if (eventType != null) { if (eventType != null) {
agentEventService.recordEvent(agent.id(), agent.group(), eventType, agentEventService.recordEvent(agent.id(), agent.application(), eventType,
agent.name() + " " + before + " -> " + agent.state()); agent.name() + " " + before + " -> " + agent.state());
} }
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -13,7 +13,7 @@ import java.util.Map;
* *
* @param id agent-provided persistent identifier * @param id agent-provided persistent identifier
* @param name human-readable agent name * @param name human-readable agent name
* @param group logical grouping (e.g., "order-service-prod") * @param application application name (e.g., "order-service-prod")
* @param version agent software version * @param version agent software version
* @param routeIds list of Camel route IDs managed by this agent * @param routeIds list of Camel route IDs managed by this agent
* @param capabilities agent-declared capabilities (free-form) * @param capabilities agent-declared capabilities (free-form)
@@ -25,7 +25,7 @@ import java.util.Map;
public record AgentInfo( public record AgentInfo(
String id, String id,
String name, String name,
String group, String application,
String version, String version,
List<String> routeIds, List<String> routeIds,
Map<String, Object> capabilities, Map<String, Object> capabilities,
@@ -36,28 +36,28 @@ public record AgentInfo(
) { ) {
public AgentInfo withState(AgentState newState) { public AgentInfo withState(AgentState newState) {
return new AgentInfo(id, name, group, version, routeIds, capabilities, return new AgentInfo(id, name, application, version, routeIds, capabilities,
newState, registeredAt, lastHeartbeat, staleTransitionTime); newState, registeredAt, lastHeartbeat, staleTransitionTime);
} }
public AgentInfo withLastHeartbeat(Instant newLastHeartbeat) { public AgentInfo withLastHeartbeat(Instant newLastHeartbeat) {
return new AgentInfo(id, name, group, version, routeIds, capabilities, return new AgentInfo(id, name, application, version, routeIds, capabilities,
state, registeredAt, newLastHeartbeat, staleTransitionTime); state, registeredAt, newLastHeartbeat, staleTransitionTime);
} }
public AgentInfo withRegisteredAt(Instant newRegisteredAt) { public AgentInfo withRegisteredAt(Instant newRegisteredAt) {
return new AgentInfo(id, name, group, version, routeIds, capabilities, return new AgentInfo(id, name, application, version, routeIds, capabilities,
state, newRegisteredAt, lastHeartbeat, staleTransitionTime); state, newRegisteredAt, lastHeartbeat, staleTransitionTime);
} }
public AgentInfo withStaleTransitionTime(Instant newStaleTransitionTime) { public AgentInfo withStaleTransitionTime(Instant newStaleTransitionTime) {
return new AgentInfo(id, name, group, version, routeIds, capabilities, return new AgentInfo(id, name, application, version, routeIds, capabilities,
state, registeredAt, lastHeartbeat, newStaleTransitionTime); state, registeredAt, lastHeartbeat, newStaleTransitionTime);
} }
public AgentInfo withMetadata(String name, String group, String version, public AgentInfo withMetadata(String name, String application, String version,
List<String> routeIds, Map<String, Object> capabilities) { List<String> routeIds, Map<String, Object> capabilities) {
return new AgentInfo(id, name, group, version, routeIds, capabilities, return new AgentInfo(id, name, application, version, routeIds, capabilities,
state, registeredAt, lastHeartbeat, staleTransitionTime); state, registeredAt, lastHeartbeat, staleTransitionTime);
} }
} }

View File

@@ -43,10 +43,10 @@ public class AgentRegistryService {
* Register a new agent or re-register an existing one. * Register a new agent or re-register an existing one.
* Re-registration updates metadata, transitions state to LIVE, and resets timestamps. * Re-registration updates metadata, transitions state to LIVE, and resets timestamps.
*/ */
public AgentInfo register(String id, String name, String group, String version, public AgentInfo register(String id, String name, String application, String version,
List<String> routeIds, Map<String, Object> capabilities) { List<String> routeIds, Map<String, Object> capabilities) {
Instant now = Instant.now(); Instant now = Instant.now();
AgentInfo newAgent = new AgentInfo(id, name, group, version, AgentInfo newAgent = new AgentInfo(id, name, application, version,
List.copyOf(routeIds), Map.copyOf(capabilities), List.copyOf(routeIds), Map.copyOf(capabilities),
AgentState.LIVE, now, now, null); AgentState.LIVE, now, now, null);
@@ -55,13 +55,13 @@ public class AgentRegistryService {
// Re-registration: update metadata, reset to LIVE // Re-registration: update metadata, reset to LIVE
log.info("Agent {} re-registering (was {})", id, existing.state()); log.info("Agent {} re-registering (was {})", id, existing.state());
return existing return existing
.withMetadata(name, group, version, List.copyOf(routeIds), Map.copyOf(capabilities)) .withMetadata(name, application, version, List.copyOf(routeIds), Map.copyOf(capabilities))
.withState(AgentState.LIVE) .withState(AgentState.LIVE)
.withLastHeartbeat(now) .withLastHeartbeat(now)
.withRegisteredAt(now) .withRegisteredAt(now)
.withStaleTransitionTime(null); .withStaleTransitionTime(null);
} }
log.info("Agent {} registered (name={}, group={})", id, name, group); log.info("Agent {} registered (name={}, application={})", id, name, application);
return newAgent; return newAgent;
}); });
@@ -168,11 +168,11 @@ public class AgentRegistryService {
} }
/** /**
* Return all agents belonging to the given application group. * Return all agents belonging to the given application.
*/ */
public List<AgentInfo> findByGroup(String group) { public List<AgentInfo> findByApplication(String application) {
return agents.values().stream() return agents.values().stream()
.filter(a -> group.equals(a.group())) .filter(a -> application.equals(a.application()))
.collect(Collectors.toList()); .collect(Collectors.toList());
} }

View File

@@ -14,21 +14,21 @@ public interface JwtService {
/** /**
* Validated JWT payload. * Validated JWT payload.
* *
* @param subject the {@code sub} claim (agent ID or {@code user:<username>}) * @param subject the {@code sub} claim (agent ID or {@code user:<username>})
* @param group the {@code group} claim * @param application the {@code group} claim (application name)
* @param roles the {@code roles} claim (e.g. {@code ["AGENT"]}, {@code ["ADMIN"]}) * @param roles the {@code roles} claim (e.g. {@code ["AGENT"]}, {@code ["ADMIN"]})
*/ */
record JwtValidationResult(String subject, String group, List<String> roles) {} record JwtValidationResult(String subject, String application, List<String> roles) {}
/** /**
* Creates a signed access JWT with the given subject, group, and roles. * Creates a signed access JWT with the given subject, application, and roles.
*/ */
String createAccessToken(String subject, String group, List<String> roles); String createAccessToken(String subject, String application, List<String> roles);
/** /**
* Creates a signed refresh JWT with the given subject, group, and roles. * Creates a signed refresh JWT with the given subject, application, and roles.
*/ */
String createRefreshToken(String subject, String group, List<String> roles); String createRefreshToken(String subject, String application, List<String> roles);
/** /**
* Validates an access token and returns the full validation result. * Validates an access token and returns the full validation result.
@@ -46,12 +46,12 @@ public interface JwtService {
// --- Backward-compatible defaults (delegate to role-aware methods) --- // --- Backward-compatible defaults (delegate to role-aware methods) ---
default String createAccessToken(String subject, String group) { default String createAccessToken(String subject, String application) {
return createAccessToken(subject, group, List.of()); return createAccessToken(subject, application, List.of());
} }
default String createRefreshToken(String subject, String group) { default String createRefreshToken(String subject, String application) {
return createRefreshToken(subject, group, List.of()); return createRefreshToken(subject, application, List.of());
} }
default String validateAndExtractAgentId(String token) { default String validateAndExtractAgentId(String token) {

View File

@@ -32,7 +32,7 @@ class AgentRegistryServiceTest {
assertThat(agent).isNotNull(); assertThat(agent).isNotNull();
assertThat(agent.id()).isEqualTo("agent-1"); assertThat(agent.id()).isEqualTo("agent-1");
assertThat(agent.name()).isEqualTo("Order Agent"); assertThat(agent.name()).isEqualTo("Order Agent");
assertThat(agent.group()).isEqualTo("order-svc"); assertThat(agent.application()).isEqualTo("order-svc");
assertThat(agent.version()).isEqualTo("1.0.0"); assertThat(agent.version()).isEqualTo("1.0.0");
assertThat(agent.routeIds()).containsExactly("route1", "route2"); assertThat(agent.routeIds()).containsExactly("route1", "route2");
assertThat(agent.capabilities()).containsEntry("feature", "tracing"); assertThat(agent.capabilities()).containsEntry("feature", "tracing");
@@ -52,7 +52,7 @@ class AgentRegistryServiceTest {
assertThat(updated.id()).isEqualTo("agent-1"); assertThat(updated.id()).isEqualTo("agent-1");
assertThat(updated.name()).isEqualTo("New Name"); assertThat(updated.name()).isEqualTo("New Name");
assertThat(updated.group()).isEqualTo("new-group"); assertThat(updated.application()).isEqualTo("new-group");
assertThat(updated.version()).isEqualTo("2.0.0"); assertThat(updated.version()).isEqualTo("2.0.0");
assertThat(updated.routeIds()).containsExactly("route1", "route2"); assertThat(updated.routeIds()).containsExactly("route1", "route2");
assertThat(updated.capabilities()).containsEntry("new", "cap"); assertThat(updated.capabilities()).containsEntry("new", "cap");

File diff suppressed because one or more lines are too long

View File

@@ -3,12 +3,12 @@ import { api } from '../client';
import { config } from '../../config'; import { config } from '../../config';
import { useAuthStore } from '../../auth/auth-store'; import { useAuthStore } from '../../auth/auth-store';
export function useAgents(status?: string, group?: string) { export function useAgents(status?: string, application?: string) {
return useQuery({ return useQuery({
queryKey: ['agents', status, group], queryKey: ['agents', status, application],
queryFn: async () => { queryFn: async () => {
const { data, error } = await api.GET('/agents', { const { data, error } = await api.GET('/agents', {
params: { query: { ...(status ? { status } : {}), ...(group ? { group } : {}) } }, params: { query: { ...(status ? { status } : {}), ...(application ? { application } : {}) } },
}); });
if (error) throw new Error('Failed to load agents'); if (error) throw new Error('Failed to load agents');
return data!; return data!;

View File

@@ -28,7 +28,7 @@ export function useDiagramByRoute(application: string | undefined, routeId: stri
queryKey: ['diagrams', 'byRoute', application, routeId], queryKey: ['diagrams', 'byRoute', application, routeId],
queryFn: async () => { queryFn: async () => {
const { data, error } = await api.GET('/diagrams', { const { data, error } = await api.GET('/diagrams', {
params: { query: { group: application!, routeId: routeId! } }, params: { query: { application: application!, routeId: routeId! } },
}); });
if (error) throw new Error('Failed to load diagram for route'); if (error) throw new Error('Failed to load diagram for route');
return data!; return data!;

View File

@@ -625,10 +625,10 @@ export interface paths {
cookie?: never; cookie?: never;
}; };
/** /**
* Find diagram by application group and route ID * Find diagram by application and route ID
* @description Resolves group to agent IDs and finds the latest diagram for the route * @description Resolves application to agent IDs and finds the latest diagram for the route
*/ */
get: operations["findByGroupAndRoute"]; get: operations["findByApplicationAndRoute"];
put?: never; put?: never;
post?: never; post?: never;
delete?: never; delete?: never;
@@ -683,7 +683,7 @@ export interface paths {
}; };
/** /**
* List all agents * 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
*/ */
get: operations["listAgents"]; get: operations["listAgents"];
put?: never; put?: never;
@@ -1158,7 +1158,7 @@ export interface components {
agentId: string; agentId: string;
name: string; name: string;
/** @default default */ /** @default default */
group: string; application: string;
version?: string; version?: string;
routeIds?: string[]; routeIds?: string[];
capabilities?: { capabilities?: {
@@ -1384,7 +1384,7 @@ export interface components {
AgentInstanceResponse: { AgentInstanceResponse: {
id: string; id: string;
name: string; name: string;
group: string; application: string;
status: string; status: string;
routeIds: string[]; routeIds: string[];
/** Format: date-time */ /** Format: date-time */
@@ -3133,10 +3133,10 @@ export interface operations {
}; };
}; };
}; };
findByGroupAndRoute: { findByApplicationAndRoute: {
parameters: { parameters: {
query: { query: {
group: string; application: string;
routeId: string; routeId: string;
}; };
header?: never; header?: never;
@@ -3154,7 +3154,7 @@ export interface operations {
"*/*": components["schemas"]["DiagramLayout"]; "*/*": components["schemas"]["DiagramLayout"];
}; };
}; };
/** @description No diagram found for the given group and route */ /** @description No diagram found for the given application and route */
404: { 404: {
headers: { headers: {
[name: string]: unknown; [name: string]: unknown;
@@ -3239,7 +3239,7 @@ export interface operations {
parameters: { parameters: {
query?: { query?: {
status?: string; status?: string;
group?: string; application?: string;
}; };
header?: never; header?: never;
path?: never; path?: never;

View File

@@ -63,7 +63,7 @@ function AgentOverviewContent({ agent }: { agent: any }) {
<dl className={styles.detailList}> <dl className={styles.detailList}>
<div className={styles.detailRow}> <div className={styles.detailRow}>
<dt>Application</dt> <dt>Application</dt>
<dd><MonoText>{agent.group ?? '—'}</MonoText></dd> <dd><MonoText>{agent.application ?? '—'}</MonoText></dd>
</div> </div>
<div className={styles.detailRow}> <div className={styles.detailRow}>
<dt>Version</dt> <dt>Version</dt>
@@ -175,7 +175,7 @@ export default function AgentHealth() {
const agentsByApp = useMemo(() => { const agentsByApp = useMemo(() => {
const map: Record<string, any[]> = {}; const map: Record<string, any[]> = {};
(agents || []).forEach((a: any) => { (agents || []).forEach((a: any) => {
const g = a.group; const g = a.application;
if (!map[g]) map[g] = []; if (!map[g]) map[g] = [];
map[g].push(a); map[g].push(a);
}); });
@@ -185,7 +185,7 @@ export default function AgentHealth() {
const liveCount = (agents || []).filter((a: any) => a.status === 'LIVE').length; const liveCount = (agents || []).filter((a: any) => a.status === 'LIVE').length;
const staleCount = (agents || []).filter((a: any) => a.status === 'STALE').length; const staleCount = (agents || []).filter((a: any) => a.status === 'STALE').length;
const deadCount = (agents || []).filter((a: any) => a.status === 'DEAD').length; const deadCount = (agents || []).filter((a: any) => a.status === 'DEAD').length;
const uniqueApps = new Set((agents || []).map((a: any) => a.group)).size; const uniqueApps = new Set((agents || []).map((a: any) => a.application)).size;
const activeRoutes = (agents || []).filter((a: any) => a.status === 'LIVE').reduce((sum: number, a: any) => sum + (a.activeRoutes || 0), 0); const activeRoutes = (agents || []).filter((a: any) => a.status === 'LIVE').reduce((sum: number, a: any) => sum + (a.activeRoutes || 0), 0);
const totalTps = (agents || []).filter((a: any) => a.status === 'LIVE').reduce((sum: number, a: any) => sum + (a.tps || 0), 0); const totalTps = (agents || []).filter((a: any) => a.status === 'LIVE').reduce((sum: number, a: any) => sum + (a.tps || 0), 0);