Changelog
Every notable change ships here, newest first. The format follows Keep a Changelog and the project uses Semantic Versioning. Each entry names the surface it touches — controller, daemon, CLI, dashboard, modules, plugins — so you can scan for the parts you actually run.
Upgrading? Read Upgrading for the routine v1.x path. The v1.0 → v1.1 hop is a one-time, one-way migration with its own runbook.
Unreleased
Work landed on main after v1.1, not yet cut as a release.
Added
- OpenTelemetry tracing, opt-in. Off by default; enable with
telemetry.enabled=true(controller and daemon each carry their own block). When disabled, a no-op tracer is installed and the SDK is never built, so there is no runtime cost. When enabled, spans export over OTLP to any compatible collector and trace context propagates controller → daemon over the gRPC command stream. Prometheus stays the metrics surface. See ADR 30. - Signed module registries.
modules.registriestakes a list of static, signed JSON index URLs. The controller can browse, resolve, download, and install a module by id viaprexorctl module search / install / upgrade. The index is discovery only — every install still verifies sha256 and signature against the controller’s own trust root. See ADR 31. - Bedrock support via Geyser. A
GEYSERplatform target plus a daemon that provisions a Geyser sidecar with dynamic remote-proxy resolution. The controller tracks each player’s edition (Java vs Bedrock) and the dashboard shows it. - Edition-aware Bedrock routing in Network Composition. Networks can route Bedrock players to dedicated lobby and fallback groups, separate from the Java chain.
- Modded server targets. Fabric (Loom) and NeoForge (ModDevGradle) server mods join the Paper / Spigot / Folia and Velocity / BungeeCord plugin set.
Changed
- Design tokens are CI-guarded, not imported.
design-system/is the canonical token source; each surface mirrors the tokens in its own stack and a parity suite fails CI on drift. See ADR 32 and ADR 33.
1.1.0 — 2026-05-31
v1.1 replaces the v1.0 MongoDB-based cluster join story with an embedded Apache Ratis Raft control plane shared across the controllers. Cluster identity, versioned shared config, members, join tokens, and leader leases move into a Raft state machine. MongoDB stays the system of record for business state; Valkey stays for ephemeral fan-out. Moving from v1.0 is a one-time, one-way migration — follow the v1.0 → v1.1 runbook.
Added
- Embedded Ratis Raft cluster control plane. The N controllers form one Raft group with a typed state machine holding cluster identity, versioned config, members, join tokens, and named leader leases. No external coordinator — no etcd, Consul, or ZooKeeper. See ADR 29.
- New
controller.ymlkeysraft.host/raft.port(default9190) andraft.dir. The port only needs to be reachable peer-to-peer between controllers. New on-disk state lives underdata/raft/andconfig/security/cluster/. - Automatic controller join. A new controller boots into join mode with a
single-use token (
prexorctl cluster join-token create), submits a CSR over the gRPCClusterMembership.RequestJoinhandshake, and receives a cluster-CA signed leaf certificate. Cluster-CA-pinned mTLS protects both the Raft transport and the gRPC channel. prexorctl clustercommands —status,members,config show / patch / history,join-token create / revoke,leave, andeject.- Raft-replicated live config.
PATCH /api/v1/cluster/configwrites a new config version through the Raft log; subscribers (CORS allow-list, JWT manager, rate limiter, signing policy) react to a typed change event with no Redis round-trip. - Coarse leader leases via
ClusterLeaseManager— scheduler, deployment reconciler, DR drill, and audit pruner are leader-elected through the state machine.
Changed
- Lease + fencing moves from Valkey TTL to Raft. The active-active architecture is unchanged; the coordination substrate is now the Raft state machine, which closes the lease-expiry races of the Valkey-TTL path. See ADR 4 (mechanism superseded by ADR 29).
- New cluster permissions
CLUSTER_VIEW,CLUSTER_CONFIG_WRITE, andCLUSTER_MANAGEreplace the v1.0CLUSTER_JOINpermission. The built-in admin role gains them automatically on first v1.1 boot; custom roles bound toCLUSTER_JOINmust be re-bound.
Removed
- The v1.0 Mongo cluster-join path. The
cluster_metacollection andGET /api/v1/admin/cluster/join-templateare gone — the join endpoint now returns404.
1.0.0 — 2026-05-05
The first stable release. v1.0 closes out the API and shared-layer overhaul that reshaped the module SDK, the event bus, and the daemon-side extension surface, and lands the production-hardening work — signed modules, lease-scoped HA, the nightly DR drill.
Added
- Active-active controller HA with lease-scoped work and fencing tokens.
Multiple controllers run against the same MongoDB and Valkey; any healthy
controller serves REST and gRPC. Failover is exercised by
RecoveryTestat four 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 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 at install time. - Daemon-side modules. A new
DaemonModuleinterface incloud-apiwith instance-lifecycle hooks, manifest-drivenhosts: [controller, daemon], a gRPCModuleDistributorthat fans installs to every connected daemon, and a controller → daemon event forwarder with subscribe-registration semantics (no event firehose). - Network Composition, end to end. Controller persistence and REST surface, proxy-plugin cache, dashboard editor, and proxy-plugin routing ship together. Define lobby and fallback chains once; the proxy plugin walks them on every connect and every kick.
- First-party reference module
stats-aggregatorunderjava/cloud-modules/stats-aggregator/, demonstrating REST routes, capability registration, MongoDB-backed storage, workload extensions, and a frontend manifest in one example. - Module REST dispatcher. A
ModuleRouteRegistryplus a wildcard Javalin handler per HTTP method. Modules register routes viaPlatformModule#onRegisterRoutes; they appear at/api/v1/modules/<moduleId>/<sub>. - Capability registry events (
CapabilityRegisteredEvent,CapabilityUnregisteredEvent,CapabilityProviderChangedEvent) over the SSE bus and a dedicated/api/v1/modules/platform/capabilities/stream. The dashboard’suseCapabilitycomposable seeds from the registry and updates as providers come and go. prexorctl plugin newscaffolds standalone@CloudPluginjars, symmetric toprexorctl module new. Targets Paper / Spigot / Folia / Velocity / BungeeCord.- Email-based password reset (off by default,
security.passwordReset.enabled). Single-use 30-minute tokens; STARTTLS / implicit-TLS / AUTH viajakarta.mail; a log-onlyLogMailerfor dry runs. - Performance baseline harness with a nightly CI drift comparator — controller cold start, coordination-store latency, SSE latency, and scheduler tick at 1k groups; warns at >25% drift, never fails. See Performance benchmarks and ADR 23.
- Nightly disaster-recovery drill. The
dr-drilljob boots 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-jars.ymlsigns the controller and daemon jars, andrelease-images.ymlships cosign-signed multi-arch GHCR images on everyv*tag. prexorctl setupwith native and Docker Compose install modes, including cosign verification of the downloaded controller and daemon jars.- OpenAPI 3.0 spec at
docs/openapi.json, auto-generated from@OpenApiannotations on the controller’s route handlers. It drives the rendered REST reference and the Scalar/playground.
Changed
PlatformModuleContextis now theModuleContextinterface, addingevents(),logger(),scheduler(),httpClient(),json(), andhost(). The legacy record was deleted with no compat shim; every first-party caller migrated in the same change.- One
EventBuscontract. The controller-internalEventBusnow implementsapi.event.EventBus, so modules subscribe to controller events with the same interface plugins already use. Subscriptions returnEventSubscriptionhandles for explicit teardown. - Module manifest reshape.
backend.entrypointbecomesbackend.controller.entrypoint/backend.daemon.entrypoint, with a top-levelhosts: [controller, daemon]field. Legacy manifests still parse (mapped tocontroller). - Player Journey and Webhook Alerts are now first-party modules, not
controller built-ins. The webhook change is breaking: operators with the old
YAML
webhooks:block must migrate entries into thecloud-module-webhook-alertsmodule’s storage. - The module signature verifier moved to
cloud-security/signingso the daemon can reuse it without importingcloud-controller. cloud-commonruns on Java 21 (was Java 25 preview) so plugin-side modules can consume it without unlocking preview features.- Documentation rewritten in English with an architectural decisions
register; the pre-v1 German
CLOUD_GUIDE.mdwas removed. See ADR 24.
Removed
- OIDC / SAML / SSO support. Operator auth is now username + password + JWT, with optional email-based password reset. See ADR 8.
- The bundled Grafana dashboard pack.
/metricsexposition stays stable; build your own panels from Monitoring and metrics. See ADR 10.
Security
- Module bundles are signed with Cosign and verified fail-closed in production. Upgrading from a pre-v1 build with unsigned modules requires re-signing and re-installing before the production-profile controller will load them.
- mTLS is the only daemon authentication path. Per-node certificate revocation
via
POST /api/v1/nodes/{id}/revoke-certis enforced immediately by the mTLS interceptor. - JWT revocation is shared across controllers via Valkey in production profile. In development profile it stays in-memory and is lost on restart.