Splits the pure license contract types (LicenseInfo, LicenseValidator, LicenseState, LicenseStateMachine, LicenseLimits, DefaultTierLimits) into a new cameleer-license-api module under package com.cameleer.license. Why: cameleer-license-minter previously depended on cameleer-server-core for these types, dragging cameleer-server-core + cameleer-common onto the classpath of every minter consumer (notably cameleer-saas). The SaaS management plane has no business carrying server-runtime types — it only needs the license contract to mint and verify tokens. After: cameleer-license-minter -> cameleer-license-api (no server internals) cameleer-server-core -> cameleer-license-api cameleer-saas -> cameleer-license-minter -> cameleer-license-api Verified: mvn -pl cameleer-license-minter dependency:tree shows the minter no longer pulls cameleer-server-core or cameleer-common. Full reactor verify (-DskipITs) green: 371 tests pass. LicenseGate stays in server-core (server-runtime state holder, not contract). Closes cameleer/cameleer-server#156 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
16 KiB
16 KiB
paths
| paths | |
|---|---|
|
Core Module Key Classes
cameleer-server-core/src/main/java/com/cameleer/server/core/
agent/ — Agent lifecycle and commands
AgentRegistryService— in-memory registry (ConcurrentHashMap), register/heartbeat/lifecycleAgentInfo— record: id, name, application, environmentId, version, routeIds, capabilities, stateAgentCommand— record: id, type, targetAgent, payload, createdAt, expiresAtAgentEventService— records agent state changes, heartbeatsAgentState— enum: LIVE, STALE, DEAD, SHUTDOWNCommandType— enum for command types (config-update, deep-trace, replay, route-control, etc.)CommandStatus— enum for command acknowledgement statesCommandReply— record: command execution result from agentAgentEventRecord,AgentEventRepository— event persistence.AgentEventRepository.queryPage(...)is cursor-paginated (AgentEventPage{data, nextCursor, hasMore}); the legacy non-paginatedquery(...)path is gone.AgentEventRepository.findInWindow(env, appSlug, agentId, eventTypes, from, to, limit)returns matching events ordered by(timestamp ASC, insert_id ASC)— consumed byAgentLifecycleEvaluator.AgentEventPage— record:(List<AgentEventRecord> data, String nextCursor, boolean hasMore)returned byAgentEventRepository.queryPageAgentEventListener— callback interface for agent eventsRouteStateRegistry— tracks per-agent route states
runtime/ — App/Environment/Deployment domain
App— record: id, environmentId, slug, displayName, containerConfig (JSONB)AppVersion— record: id, appId, version, jarPath, detectedRuntimeType, detectedMainClassEnvironment— record: id, slug, displayName, production, enabled, defaultContainerConfig, jarRetentionCount, color, createdAt, executionRetentionDays, logRetentionDays, metricRetentionDays.coloris one of the 8 preset palette values validated byEnvironmentColor.VALUESand CHECK-constrained in PostgreSQL (V2 migration). The 3 retention day fields (V5) areint-typed (not nullable, since unlimited has no use-case), default to 1 day per the V5NOT NULL DEFAULT 1, validated >= 1 in the canonical constructor.EnvironmentColor— constants:DEFAULT = "slate",VALUES = {slate,red,amber,green,teal,blue,purple,pink},isValid(String).Deployment— record: id, appId, appVersionId, environmentId, status, targetState, deploymentStrategy, replicaStates (JSONB), deployStage, containerId, containerName, createdBy (String, user_id reference; nullable for pre-V4 historical rows)DeploymentStatus— enum: STOPPED, STARTING, RUNNING, DEGRADED, STOPPING, FAILED.DEGRADEDis reserved for post-deploy drift (a replica died after RUNNING);DeploymentExecutornow marks partial-healthy deploys FAILED, not DEGRADED.DeployStage— enum: PRE_FLIGHT, PULL_IMAGE, CREATE_NETWORK, START_REPLICAS, HEALTH_CHECK, SWAP_TRAFFIC, COMPLETEDeploymentStrategy— enum: BLUE_GREEN, ROLLING. Stored onResolvedContainerConfig.deploymentStrategyas kebab-case string ("blue-green"/"rolling").fromWire(String)is the only conversion entry point; unknown/null inputs fall back to BLUE_GREEN so the executor dispatch site never null-checks or throws.DeploymentService— createDeployment (callsdeleteFailedByAppAndEnvironmentfirst so FAILED rows don't pile up; STOPPED rows are preserved as restorable checkpoints), markRunning, markFailed, markStoppedRuntimeType— enum: AUTO, SPRING_BOOT, QUARKUS, PLAIN_JAVA, NATIVERuntimeDetector— probes JAR files at upload time: detects runtime from manifest Main-Class (Spring Boot loader, Quarkus entry point, plain Java) or native binary (non-ZIP magic bytes)ContainerRequest— record: 20 fields for Docker container creation (includes runtimeType, customArgs, mainClass)ContainerStatus— record: state, running, exitCode, errorResolvedContainerConfig— record: typed config with memoryLimitMb, memoryReserveMb, cpuRequest, cpuLimit, appPort, exposedPorts, customEnvVars, stripPathPrefix, sslOffloading, routingMode, routingDomain, serverUrl, replicas, deploymentStrategy, routeControlEnabled, replayEnabled, runtimeType, customArgs, extraNetworks, externalRouting (defaulttrue; whenfalse,TraefikLabelBuilderstrips alltraefik.*labels so the container is not publicly routed), certResolver (server-wide, sourced fromCAMELEER_SERVER_RUNTIME_CERTRESOLVER; when blank thetls.certresolverlabel is omitted — use for dev installs with a static TLS store)RoutingMode— enum for routing strategiesConfigMerger— pure function: resolve(globalDefaults, envConfig, appConfig) -> ResolvedContainerConfigRuntimeOrchestrator— interface: startContainer, stopContainer, getContainerStatus, getLogs, startLogCapture, stopLogCaptureAppRepository,AppVersionRepository,EnvironmentRepository,DeploymentRepository— repository interfacesAppService,EnvironmentService— domain servicesCreateGuard—@FunctionalInterface.void check(long current)— implementations throw to abort creation.NOOPconstant is the default. Consulted byEnvironmentService.create,AppService.createApp, andAgentRegistryService.registerso license caps can be enforced from the app module without leaking Spring or app-only types into core. Wired inLicenseBeanConfigto aLicenseEnforcer.assertWithinCap(...)call per limit key.
license/ — License domain (signed-token tier system)
The pure license contract types live in the separate cameleer-license-api module under package com.cameleer.license (no Spring, no server-runtime deps) so consumers like cameleer-license-minter and cameleer-saas can use them without inheriting server internals. Server-core only contains the runtime state holder (LicenseGate).
Contract types in cameleer-license-api (package com.cameleer.license):
LicenseInfo— record:(UUID licenseId, String tenantId, String label, Map<String,Integer> limits, Instant issuedAt, Instant expiresAt, int gracePeriodDays).isExpired()true oncenow > expiresAt + gracePeriodDays;isAfterRawExpiry()true oncenow > expiresAt. Constructed viaLicenseValidator; canonical ctor null-checks all required fields and rejects blank tenantId / negative grace.LicenseLimits— typed limits container backed byMap<String,Integer>.defaultsOnly()returns theDefaultTierLimits.DEFAULTSview;mergeOverDefaults(overrides)produces the license-overrides UNION default tier.get(String key)returns the cap; throwsIllegalArgumentExceptionfor unknown keys (programmer error).isDefaultSourced(key, license)reports whether a key fell through to the default tier.DefaultTierLimits— immutableLinkedHashMapof constants for the no-license fallback tier:max_environments=1, max_apps=3, max_agents=5, max_users=3, max_outbound_connections=1, max_alert_rules=2, max_total_cpu_millis=2000, max_total_memory_mb=2048, max_total_replicas=5, max_execution_retention_days=1, max_log_retention_days=1, max_metric_retention_days=1, max_jar_retention_count=3.LicenseValidator— verifies signed token. Constructor(String publicKeyBase64, String expectedTenantId)decodes an X.509 Ed25519 public key.validate(String token)splitspayload.signature, verifies the Ed25519 signature, parses the JSON payload, enforcestenantId == expectedTenantId, and returnsLicenseInfo. ThrowsSecurityExceptionon signature mismatch /IllegalArgumentExceptionon parse failure / expired payload.LicenseStateMachine— pure classifier.classify(LicenseInfo, String invalidReason)returnsINVALIDif a reason is set,ABSENTif no license,ACTIVEifnow <= expiresAt,GRACEif expired but within grace window,EXPIREDotherwise.LicenseState— enum:ABSENT, ACTIVE, GRACE, EXPIRED, INVALID.
Runtime state holder in server-core (package com.cameleer.server.core.license):
LicenseGate— runtime state holder (thread-safe viaAtomicReference<Snapshot>).getCurrent()returns the currentLicenseInfo(null when ABSENT/INVALID);getState()delegates toLicenseStateMachine.classify(...);getEffectiveLimits()returns license-overrides UNION defaults inACTIVE/GRACE, defaults-only otherwise.getInvalidReason(),load(LicenseInfo),markInvalid(String reason),clear()are the mutators.getLimit(key, defaultValue)shorthand swallows unknown-key errors.
search/ — Execution search and stats
SearchService— search, count, stats, statsForApp, statsForRoute, timeseries, timeseriesForApp, timeseriesForRoute, timeseriesGroupedByApp, timeseriesGroupedByRoute, slaCompliance, slaCountsByApp, slaCountsByRoute, topErrors, activeErrorTypes, punchcard, distinctAttributeKeys.statsForRoute/timeseriesForRoutetake(routeId, applicationId)— app filter is applied tostats_1m_route.SearchRequest/SearchResult— search DTOs.SearchRequest.attributeFilters: List<AttributeFilter>carries structured facet filters for execution attributes — key-only (exists), exact (key=value), or wildcard (*in value). The 21-arg legacy ctor is preserved for call-site churn; the compact ctor normalises null →List.of().AttributeFilter(key, value)— record with key regex^[a-zA-Z0-9._-]+$(inlined into SQL, same constraint as alerting),value == nullmeans key-exists,valuecontaining*becomes a SQL LIKE pattern viatoLikePattern().ExecutionStats,ExecutionSummary— stats aggregation recordsStatsTimeseries,TopError— timeseries and error DTOsLogSearchRequest/LogSearchResponse— log search DTOs.LogSearchRequest.sources/levelsareList<String>(null-normalized, multi-value OR);cursor+limit+sortdrive keyset pagination. Response carriesnextCursor+hasMore+ per-levellevelCounts.
storage/ — Storage abstractions
ExecutionStore,MetricsStore,MetricsQueryStore,StatsStore,DiagramStore,RouteCatalogStore,SearchIndex,LogIndex— interfaces.DiagramStore.findLatestContentHashForAppRoute(appId, routeId, env)resolves the latest diagram by (app, env, route) without consulting the agent registry, so routes whose publishing agents were removed between app versions still resolve.findContentHashForRoute(route, instance)is retained for the ingestion path that stamps a per-executiondiagramContentHashat ingest time (point-in-time link fromExecutionDetail/ExecutionSummary).RouteCatalogEntry— record: applicationId, routeId, environment, firstSeen, lastSeenLogEntryResult— log query result recordmodel/—ExecutionDocument,MetricTimeSeries,MetricsSnapshot
rbac/ — Role-based access control
RbacService— interface: role/group CRUD, assignRoleToUser, removeRoleFromUser, addUserToGroup, removeUserFromGroup, getDirectRolesForUser, getEffectiveRolesForUser, clearManagedAssignments, assignManagedRole, addUserToManagedGroup, getStats, listUsersSystemRole— enum: AGENT, VIEWER, OPERATOR, ADMIN;normalizeScope()maps scopesUserDetail,RoleDetail,GroupDetail— recordsUserSummary,RoleSummary,GroupSummary— lightweight list recordsRbacStats— aggregate stats recordAssignmentOrigin— enum: DIRECT, CLAIM_MAPPING (tracks how roles were assigned)ClaimMappingRule— record: OIDC claim-to-role mapping ruleClaimMappingService— interface: CRUD for claim mapping rulesClaimMappingRepository— persistence interfaceRoleRepository,GroupRepository— persistence interfaces
admin/ — Server-wide admin config
SensitiveKeysConfig— record: keys (List, immutable)SensitiveKeysRepository— interface: find(), save()SensitiveKeysMerger— pure function: merge(global, perApp) -> union with case-insensitive dedup, preserves first-seen casing. Returns null when both inputs null.AppSettings,AppSettingsRepository— per-app-per-env settings config and persistence. Record carries(applicationId, environment, …); repository methods arefindByApplicationAndEnvironment,findByEnvironment,save,delete(appId, env).AppSettings.defaults(appId, env)produces a default instance scoped to an environment.ThresholdConfig,ThresholdRepository— alerting threshold config and persistenceAuditService— audit logging facadeAuditRecord,AuditResult,AuditCategory(enum:INFRA, AUTH, USER_MGMT, CONFIG, RBAC, AGENT, OUTBOUND_CONNECTION_CHANGE, OUTBOUND_HTTP_TRUST_CHANGE, ALERT_RULE_CHANGE, ALERT_SILENCE_CHANGE, DEPLOYMENT, LICENSE),AuditRepository— audit trail records and persistence
http/ — Outbound HTTP primitives (cross-cutting)
OutboundHttpClientFactory— interface:clientFor(context)returns memoizedCloseableHttpClientOutboundHttpProperties— record:trustAll, trustedCaPemPaths, defaultConnectTimeout, defaultReadTimeout, proxyUrl, proxyUsername, proxyPasswordOutboundHttpRequestContext— record of per-call TLS/timeout overrides;systemDefault()static factoryTrustMode— enum:SYSTEM_DEFAULT | TRUST_ALL | TRUST_PATHS
outbound/ — Admin-managed outbound connections
OutboundConnection— record: id, tenantId, name, description, url, method, defaultHeaders, defaultBodyTmpl, tlsTrustMode, tlsCaPemPaths, hmacSecretCiphertext, auth, allowedEnvironmentIds, createdAt, createdBy (String user_id), updatedAt, updatedBy (String user_id).isAllowedInEnvironment(envId)returns true when allowed-envs list is empty OR contains the env.OutboundAuth— sealed interface + records:None | Bearer(tokenCiphertext) | Basic(username, passwordCiphertext). Jackson@JsonTypeInfo(use = DEDUCTION)— wire shape has no discriminator, subtype inferred from fields.OutboundAuthKind,OutboundMethod— enumsOutboundConnectionRepository— CRUD by (tenantId, id): save/findById/findByName/listByTenant/deleteOutboundConnectionService— create/update/delete/get/list with uniqueness + narrow-envs + delete-if-referenced guards.rulesReferencing(id)stubbed in Plan 01 (returns[]); populated in Plan 02 againstAlertRuleRepository.
security/ — Auth
JwtService— interface: createAccessToken, createRefreshToken, validateAccessToken, validateRefreshTokenEd25519SigningService— interface: sign, getPublicKeyBase64 (config signing)OidcConfig— record: enabled, issuerUri, clientId, clientSecret, rolesClaim, defaultRoles, autoSignup, displayNameClaim, userIdClaim, audience, additionalScopesOidcConfigRepository— persistence interfacePasswordPolicyValidator— min 12 chars, 3-of-4 character classes, no username matchUserInfo,UserRepository— user identity records and persistenceInvalidTokenException— thrown on revoked/expired tokens
ingestion/ — Buffered data pipeline
IngestionService— diagram + metrics facade (ingestDiagram,acceptMetrics,getMetricsBuffer). Execution ingestion went through here via the legacyRouteExecutionshape untilChunkAccumulatortook over writes from the chunked pipeline — theingestExecutionpath plus itsExecutionStore.upsert/upsertProcessorsdependencies were removed.ChunkAccumulator— batches data for efficient flush; owns the execution write path (chunks → buffers → flush scheduler →ClickHouseExecutionStore.insertExecutionBatch).WriteBuffer— bounded ring buffer for async flushBufferedLogEntry— log entry wrapper with metadataMergedExecution,TaggedDiagram— tagged ingestion records.TaggedDiagramcarries(instanceId, applicationId, environment, graph)— env is resolved from the agent registry in the controller and stamped on the ClickHouseroute_diagramsrow.