Changelog
All notable changes to PrexorCloud are recorded here. The format follows Keep a Changelog and the project uses Semantic Versioning. Each entry references the surfaces it touches — controller, daemon, CLI, dashboard, modules, plugins — so operators can scan for the parts they actually run.
Unreleased
Tracking work after v1.0. No entries yet.
1.0.0 — 2026-05-05
The first stable release. v1.0 closes out the API + shared-layer overhaul (Layers 1 through 9) that re-shaped the module SDK, the event bus, and the daemon-side extension surface. It also lands the production-hardening work — signed modules, lease-scoped HA, the nightly DR drill — that we wanted in the box before calling anything “v1.”
Added
- Active-active controller HA with lease-scoped work and fencing
tokens. Multiple controllers run against the same MongoDB and
Valkey; any healthy controller can serve REST and gRPC. Failover
goes through
RecoveryTestat four exercise points (drain, deployment, placement-time, in-flight module mutation). - Cosign-signed module bundles, fail-closed in production by
default.
modules.signing.required: trueis the production default;<jar>.cosign.bundleis the new sidecar format alongside the legacy<jar>.sig. - Offline Rekor SET enforcement via
modules.signing.rekor.policy=REQUIRE_SET. The controller verifies the bundle’sSignedEntryTimestampagainst a locally-bundled Rekor public key — no network access required at install time. - Daemon-side modules (Layer 7). New
DaemonModuleinterface incloud-apiwith instance-lifecycle hooks (onInstanceStarting / Started / Stopping / Stopped), manifest-drivenhosts: [controller, daemon], gRPC-basedModuleDistributorthat fans installs to every connected daemon, and a controller→daemonDaemonEventForwarderwith subscribe-registration semantics (no event firehose). - Network Composition end-to-end. Controller-side persistence and REST surface, proxy-plugin cache, dashboard editor, and proxy-plugin routing all ship together. Operators define lobby and fallback chains once, and the proxy plugin walks them on every player connect and on every kick.
- First-party reference module:
stats-aggregatorunderjava/cloud-module/cloud-module-stats-aggregator/. Demonstrates REST routes, capability registration, MongoDB-backed storage, workload extensions, and a frontend manifest in one well-documented example. - Module REST dispatcher. A
ModuleRouteRegistryplus a wildcard Javalin handler per HTTP method. Modules register routes viaPlatformModule#onRegisterRoutesand they appear at/api/v1/modules/<moduleId>/<sub>atomically. - Capability registry events (Layer 8).
CapabilityRegisteredEvent,CapabilityUnregisteredEvent, andCapabilityProviderChangedEventflow through the SSE bus and the new dedicated/api/v1/modules/platform/capabilities/stream. The dashboard’suseCapabilitycomposable seeds from the registry and reactively updates when providers come and go. prexorctl plugin newfor scaffolding standalone@CloudPluginjars (Path A) symmetrically toprexorctl module new. Supports Paper / Spigot / Folia / Velocity / BungeeCord targets.- Email-based password reset (off by default, off-switch is
security.passwordReset.enabled). Single-use 30-minute tokens, STARTTLS / implicit-TLS / AUTH support viajakarta.mail, log-onlyLogMailerfor dry runs. - Performance baseline harness with a CI drift comparator.
Nightly run measures controller cold start, coordination-store
latency, SSE latency, and scheduler tick at 1k groups; warns at
25 % drift (does not fail — see ADR 23).
- Nightly disaster-recovery drill. The
dr-drilljob in.github/workflows/nightly.ymlboots an in-process controller against a real Mongo and Valkey, takes a backup, drops the database, restores from the manifest, and asserts the restored state matches the seed. - Cosign-signed release pipeline.
release.ymlships cosign-signedprexorctlbinaries;release-images.ymlships cosign-signed multi-arch GHCR controller and daemon images on everyv*tag. - Cosign install integration test.
CosignSignedModuleInstallTestcovers the negative path — signature-mismatch installs return422 SIGNATURE_VERIFICATION_FAILED. - Cross-cutting
cloud-commoninfrastructure.HttpClients.defaults()/defaultClient(),ObjectMappers.standard() / strict() / yaml(), andBackoff.withRetries(...)are the shared building blocks every module and plugin component now uses. - Gradle platform
cloud-platform. Ajava-platformsubproject pinning Jackson, SLF4J, Logback, Paper / Velocity / Bungee / Adventure versions in one place. Every consumer pullsimplementation(platform(project(":cloud-platform"))). AbstractPluginContextshared base for plugin platforms.BukkitPluginContextandVelocityPluginContextshrunk from ~100-line constructors to ~10.prexorctl setupwith native and Compose install modes, including cosign verification of the downloaded controller / daemon jars.- OpenAPI 3.0 spec at
docs/openapi.json. Auto-generated from@OpenApiannotations on the controller’s route handlers via thejavalin-openapi-pluginannotation processor — 155 paths, 179 operations. Drives the rendered REST reference and the Scalar/playground.
Changed
PlatformModuleContextis nowModuleContext(Layer 3+4). The context interface addsevents(),logger(),scheduler(),httpClient(),json(), andhost(). The legacyPlatformModuleContextrecord was deleted with no compat shim — every first-party caller was migrated in the same change.- One
EventBuscontract. The controller-internalEventBusnow implementsapi.event.EventBus, which means modules can subscribe to controller events with the same interface plugins already used. Subscriptions returnEventSubscriptionhandles for explicit teardown. - Module manifest reshape.
backend.entrypointis nowbackend.controller.entrypointandbackend.daemon.entrypoint, with a top-levelhosts: [controller, daemon]field. Legacy manifests still parse (mapped tocontroller). - Player Journey is now a first-party module, not a controller
built-in. The new
cloud-module-player-journeymodule owns theprexor.player.journey@1.0.0capability and the underlying Mongo storage. - Webhook alerts is now a first-party module. The new
cloud-module-webhook-alertsmodule owns webhook configuration in its own Mongo collection. Operators with the old YAMLwebhooks:block need to migrate their entries to the new module’s storage — this is a breaking change. - Platform module signature verifier moved to
cloud-security/signingso the daemon can reuse it without importingcloud-controller. Verifier input is now a smallVerificationInput(sourceJar, moduleId, moduleVersion, sha256)record. cloud-commonruns on Java 21 (was Java 25 preview) so the plugin-side modules can consume it without unlocking preview features.- Documentation rewritten in English with an explicit
architectural decisions register.
The pre-v1 German
CLOUD_GUIDE.mdwas removed in favour of one source of truth — see ADR 24.
Removed
- OIDC / SAML / SSO support. The OIDC code path was removed in the v1.0 cleanup. Operator auth is now strictly username + password + JWT, with optional email-based password reset. See ADR 8.
- Bundled Grafana dashboard pack.
/metricsexposition stays stable; the dashboard pack and provisioning manifests were dropped. See ADR 10 and observability for PromQL examples operators can drop into their own Grafana boards. PlatformModuleContextrecord — replaced by theModuleContextinterface. No deprecation window; first-party callers migrated in the same change.- Controller-side
PlayerJourneyService— extracted into thecloud-module-player-journeymodule. - Controller-side
WebhookAlertServiceandwebhooks:config block — extracted into thecloud-module-webhook-alertsmodule. @controllersentinel module id — no longer used for built-in capability registration.
Fixed
- Module classloader pinning on capability rebind. The dynamic
CapabilityHandleproxy cache used to retainClass<?>keys from unloaded modules; the registry now nulls the delegate and clears the cache on deactivation, so the classloader collects. - Frontend cache pinning on module unload.
ModuleFrontendManagernow deletes the on-disk asset directory and removes the cachedLoadedFrontendso module unload no longer leaks dashboard pages. - EventBus retention on daemon disconnect.
DaemonServiceImpl.cleanup()callsforwarder.onDisconnect(nodeId)so the controller’s event bus does not retain references to dead session observers.
Security
- Module bundles are signed with Cosign and verified fail-closed in production (see Added). Operators who upgrade from a pre-v1 development build with unsigned modules need to re-sign and re-install before the controller will load them in production profile.
- mTLS is now the only daemon authentication path. Per-node
certificate revocation via
POST /api/v1/nodes/{id}/revoke-certis enforced by the mTLS interceptor immediately. - JWT revocation is shared across controllers via Valkey in production profile. In development profile it remains in-memory and is lost on restart.