{
  "versions": [
    {
      "version": "1.10.37",
      "date": "2026-07-21",
      "summary": "Artifact & deploy hardening from the code review — bigger uploads, safer deletes, correct namespace authz, and the deploy form no longer drops env vars.",
      "changes": [
        { "type": "fix", "text": "The deploy form no longer silently drops environment variables — custom vars, secret mappings, and config links are now actually sent (they were being lost, so apps started without their DB passwords / connection strings). Config-editor fields from other pages no longer bleed into a new deployment either" },
        { "type": "fix", "text": "Artifact uploads over ~28 MB no longer fail with a silent 413 (self-contained apps and module packages); the cap is now 2 GiB" },
        { "type": "fix", "text": "Deleting an artifact now actually frees the stored bytes on disk (previously only the metadata was removed), refuses to delete an artifact still referenced by a running app (409 with the list), and is namespace-scoped so a namespace admin can't delete across namespaces" },
        { "type": "fix", "text": "Role-downscoped API tokens are now enforced on artifact endpoints — a viewer-scoped token from an admin account can no longer upload/delete" },
        { "type": "fix", "text": "CI/CD deploy-app targets a specific artifact + namespace correctly (the redeploy endpoint now honors the artifactId and ns it's given). Auto-deploy on upload is crash-safe (updates in place instead of remove-then-add). Corrupt/truncated artifact caches self-heal instead of failing forever" },
        { "type": "improvement", "text": "Deploy failures show the server's actual error (not just 'Failed: 400'), the Deploy button guards against double-submit, artifact version lists are namespace-scoped, the System artifacts tab shows through a namespace filter, and secret reveal no longer shows the value in the wrong namespace's row" }
      ]
    },
    {
      "version": "1.10.36",
      "date": "2026-07-20",
      "summary": "Fix: manager modules (e.g. CI/CD Manager) showed a provider's version and an update badge that never cleared.",
      "changes": [
        { "type": "fix", "text": "The Modules page now reads a manager module's installed version from the manager's own deployment instead of accidentally picking up one of its provider modules. A manager whose providers share its name prefix (CI/CD Manager → cicd-builder-*, cicd-action-*) previously displayed a provider's version (e.g. v1.0.4) and a permanent 'update available' badge — clicking Update reported success but the shown version never changed. The version-matching rule now matches only the exact module id or a numeric replica suffix (cicd-0), never a named provider" },
        { "type": "fix", "text": "The source-node module auto-updater no longer overwrites a manager's provider deployments with the manager's own package when auto-updating the manager" }
      ]
    },
    {
      "version": "1.10.35",
      "date": "2026-07-20",
      "summary": "Windows nodes run as a real Windows Service (auto-start + auto-restart), matching Linux systemd.",
      "changes": [
        { "type": "feature", "text": "The Windows installer now registers + starts a Windows Service 'AppCluster' (auto-start on boot, auto-restart on crash) instead of a detached background process. The node integrates with the Service Control Manager via UseWindowsService(). Pass -NoService for the old background-process behavior" },
        { "type": "improvement", "text": "Both installers now stop the running node before extracting, so re-running the installer applies an update cleanly (Windows service or process; Linux systemd restart)" }
      ]
    },
    {
      "version": "1.10.34",
      "date": "2026-07-20",
      "summary": "Node update detection — the AppCluster Versions page shows a banner when clusterius.com has a newer node version.",
      "changes": [
        { "type": "feature", "text": "The AppCluster Versions page now checks clusterius.com for a newer node version and shows an update banner (this node's version vs the latest available) when one exists" },
        { "type": "feature", "text": "The banner provides the exact re-run-installer command for the detected OS (stops the node, downloads the new version, restarts; data + cluster preserved), with the other OS's command available too" }
      ]
    },
    {
      "version": "1.10.33",
      "date": "2026-07-20",
      "summary": "Artifacts page: separate My Artifacts from System (modules), grouped by namespace.",
      "changes": [
        { "type": "improvement", "text": "The Artifacts page now has My Artifacts and System tabs — your app artifacts are no longer mixed in with the cluster-managed module artifacts. Each tab is grouped by namespace, with counts on the tab labels" },
        { "type": "improvement", "text": "The create-artifact form is hidden on the System tab (system artifacts are managed by the cluster, not created by hand)" }
      ]
    },
    {
      "version": "1.10.32",
      "date": "2026-07-20",
      "summary": "Install only the modules you need — node-only installer, on-demand module install from the GUI, and internet update checks.",
      "changes": [
        { "type": "feature", "text": "The installer now sets up the node ONLY by default; install the modules you need from the GUI (each downloads from clusterius.com on demand). Pass --all-modules / -AllModules to install all 40, or --only / -Only <ids> for a subset" },
        { "type": "feature", "text": "Reconciler modules (reconciler-dotnet/nodejs/docker) can now be installed from the GUI on a source-less node — they download from the upstream registry like every other module" },
        { "type": "feature", "text": "The Modules page shows an update badge when clusterius.com has a newer version of an installed module (source-less nodes read the latest version from the upstream manifest instead of a local source tree)" }
      ]
    },
    {
      "version": "1.10.31",
      "date": "2026-07-20",
      "summary": "Upstream module registry — nodes fetch module packages from the internet (clusterius.com) when not found on a peer or locally.",
      "changes": [
        { "type": "feature", "text": "When a module package isn't available on a peer or in a local source tree, the node downloads it from the upstream distribution site (default clusterius.com) — covers both the deploy/reconcile path and the source-less GUI Install button" },
        { "type": "feature", "text": "Configurable via Node:ModuleUpstreamUrl; set null/empty to disable the internet fallback for air-gapped clusters" },
        { "type": "improvement", "text": "Hardened download path: HTTPS-only, download host pinned to the configured site's domain, SHA-256 verified against replicated cluster metadata on the deploy path, no auto-redirect, and manifest fields type/path validated" }
      ]
    },
    {
      "version": "1.10.30",
      "date": "2026-07-19",
      "summary": "Unattended one-command install (Windows + Linux) with cross-platform binaries and full module install.",
      "changes": [
        { "type": "feature", "text": "AppCluster.Node --non-interactive / APPCLUSTER_NONINTERACTIVE=1 skips the first-run setup menu and bootstraps a solo cluster — enables fully unattended install" },
        { "type": "feature", "text": "One-command bootstrap installers (install.ps1 / install.sh): install the .NET runtime if missing, start the node, and install all 40 modules over the API" },
        { "type": "feature", "text": "Portable cross-platform binaries — one node + module set runs on both Windows and Linux via the .NET muxer (no per-arch build)" },
        { "type": "fix", "text": "Critical authorization fix: GET /api/cluster/join-key now requires admin (was readable by any authenticated principal, leaking the cluster secret)" }
      ]
    },
    {
      "version": "1.5.2",
      "date": "2026-05-09",
      "summary": "GUI native-dialog cleanup — every alert / confirm / prompt replaced with HTML modal equivalents.",
      "changes": [
        { "type": "improvement", "text": "Replaced all 56 remaining browser-native dialog calls across 17 GUI files with showModal / showToast / showInfoModal / showInputModal / showFormModal helpers" },
        { "type": "feature", "text": "New showFormModal helper for chained-prompt workflows — multi-field form (text / textarea / password) with required-field validation" },
        { "type": "feature", "text": "New showInputModal helper as the prompt() replacement — Enter submits, Esc cancels, supports password mode" },
        { "type": "improvement", "text": "Docker artifact creation + image-version registration on deploy.html now use a single in-page form modal each instead of 2-3 chained native prompts" }
      ]
    },
    {
      "version": "1.5.1",
      "date": "2026-05-09",
      "summary": "Profile / user follow-ups: logout endpoint, lock-with-reason modal, integration tests.",
      "changes": [
        { "type": "feature", "text": "POST /api/auth/logout — marks current session revoked + deletes its UserApiToken. Frontend logout() calls it via keepalive fetch before clearing localStorage" },
        { "type": "feature", "text": "Lock-with-reason modal (showReasonModal) — admin lock/unlock now collects reason text, surfaced in the audit log + status badge" },
        { "type": "feature", "text": "19 new integration tests covering Phase B (account control: lock / cutoff / force-logout / admin 2FA disable) and Phase C (sessions: creation, revoke, list, logout endpoint, cascading delete)" }
      ]
    },
    {
      "version": "1.5.0",
      "date": "2026-05-09",
      "summary": "Phase C — per-session tracking with IP / user-agent capture, last-seen heartbeat, granular revoke.",
      "changes": [
        { "type": "feature", "text": "New UserSession replicated table (1:1 with UserApiToken) capturing IP + user agent at login, with debounced last-seen heartbeat (max one write per minute per session)" },
        { "type": "feature", "text": "All four login paths (local password, AD/LDAP, post-2FA, WebAuthn) funnel through a shared CreateSessionAndTokenAsync helper that issues paired token + session" },
        { "type": "feature", "text": "AuthMiddleware enforces per-session revocation alongside the global cutoff — kick one tab without affecting other devices" },
        { "type": "feature", "text": "GET /api/users/{id}/sessions (admin-or-self) with isCurrent flag on the caller's own session" },
        { "type": "feature", "text": "DELETE /api/users/{id}/sessions/{sessionId} — admin-or-self per-session revoke; deletes the underlying token row" },
        { "type": "feature", "text": "Sessions tab on profile page (own sessions, sign out specific devices) and user detail page (admin view)" },
        { "type": "improvement", "text": "Force-logout endpoint now also marks all active sessions revoked so the table visibly reflects the termination" },
        { "type": "improvement", "text": "Pre-Phase-C tokens (no UserSession row) still work — legacy fallback in AuthMiddleware" }
      ]
    },
    {
      "version": "1.4.1",
      "date": "2026-05-09",
      "summary": "Phase B — account lock / unlock, force-logout, admin 2FA disable, admin passkey revoke.",
      "changes": [
        { "type": "feature", "text": "UserAccount gains LockedAt + LockedReason + TokenInvalidationCutoffAt fields. AuthMiddleware short-circuits to 401 when set" },
        { "type": "feature", "text": "POST /api/users/{id}/lock + /unlock — admin-gated, distinct from Enabled (locked is the short-term 'pending review' state)" },
        { "type": "feature", "text": "POST /api/users/{id}/force-logout — admin-or-self. Bumps token cutoff and rejects every existing token issued before this instant" },
        { "type": "feature", "text": "POST /api/users/{id}/2fa/admin-disable — admin recovery path when a user has lost their authenticator app, audit-logged with 'recovery path' tag" },
        { "type": "feature", "text": "Security tab on user detail page with lock / unlock / force-logout / admin-disable-2FA / passkey revoke controls" },
        { "type": "feature", "text": "Self 'log out other sessions' button on profile Security tab" }
      ]
    },
    {
      "version": "1.4.0",
      "date": "2026-05-09",
      "summary": "Phase A — tab refactor of profile + user detail pages, user list filters, login audit emission.",
      "changes": [
        { "type": "feature", "text": "Profile page rebuilt with 7 tabs: Overview (avatar + 6 stat cards) / Account / Security / Sessions / API Tokens / My Permissions / Activity" },
        { "type": "feature", "text": "User detail page rebuilt with 8 tabs: Overview / Account / Roles / Security / Sessions / Permissions / API Tokens / Audit" },
        { "type": "feature", "text": "Tab choice persists via ?tab=... in the URL; refresh + copy-paste both restore the right tab" },
        { "type": "feature", "text": "User list page gains filter strip: search by username/email, role / status / namespace / last-active dropdowns, Clear button. Toolbar shows 'N of M' when filtered" },
        { "type": "feature", "text": "Generated initial avatars (deterministic per-username gradient palette)" },
        { "type": "feature", "text": "Activity tab on profile + Audit tab on user detail filter the audit log by username; ?userId= query parameter added to GET /api/audit" },
        { "type": "feature", "text": "Password change form added to profile (was previously only on the admin user detail page)" },
        { "type": "improvement", "text": "Login emits auth.login.success / auth.login.failure audit entries with source IP" },
        { "type": "improvement", "text": "GET /api/users/{id}/permissions loosened from admin-only to admin-or-self for the My Permissions tab" }
      ]
    },
    {
      "version": "1.3.1",
      "date": "2026-05-09",
      "summary": "RBAC Layer 2 GUI + Debugger canary migration.",
      "changes": [
        { "type": "feature", "text": "Cluster aggregator endpoint (GET /api/permissions/resource-types) walks every running module's /module-ui in parallel and unions the new 'resources' field for the GUI grant editor" },
        { "type": "feature", "text": "Users → Permissions panel with grant-permission modal. Resource-type dropdown driven by the live module catalog; selecting a type repopulates the action dropdown" },
        { "type": "feature", "text": "Debugger canary migration: declares 'instance' resource with 'debug' action via /module-ui; the cluster's debug-attach endpoint swaps RequireRole(\"admin\") for Permissions.Require(...) — namespace-scoped admins can now debug pods in their namespace without being made global admin" }
      ]
    },
    {
      "version": "1.3.0",
      "date": "2026-05-09",
      "summary": "RBAC Layer 2 foundation — per-user resource grants on top of namespace roles.",
      "changes": [
        { "type": "feature", "text": "New UserPermission replicated state with selector fields (resource type / namespace / name / action) supporting wildcards" },
        { "type": "feature", "text": "GET /api/auth/permissions decision API combining Layer 1 (namespace role) + Layer 2 (resource grants), OR composition" },
        { "type": "feature", "text": "POST/DELETE /api/users/{id}/permissions — admin-side CRUD with audit-logged grant / revoke" },
        { "type": "feature", "text": "Permissions.Require(context, state, resource, action, ns, name) helper for cluster-side endpoints to migrate from RequireRole at their own pace" },
        { "type": "feature", "text": "AppClusterClient.CheckPermissionAsync — SDK helper for modules with their own user-facing endpoints, forwards the user's bearer token to the cluster decision API" },
        { "type": "feature", "text": "52 unit tests pinning the decision matrix (Layer 1 role taxonomy, Layer 2 wildcard matching, OR composition, edge cases like operator-trying-to-delete)" }
      ]
    },
    {
      "version": "1.2.4",
      "date": "2026-05-09",
      "summary": "RuntimeInstaller publishes per-major Node.js capabilities immediately on install.",
      "changes": [
        { "type": "improvement", "text": "Install button's post-install probe now enumerates Node.js installs (PATH + nvm + nvm-windows) and publishes runtime-nodejs-{major} capability flags + version labels in the same NodeConfig write that flips runtime-nodejs=true. Apps pinning engines.node can be scheduled immediately, without waiting for the reconciler module to register on its own" },
        { "type": "improvement", "text": "Generalized PublishRuntimeCapabilityAsync's per-major handling — both .NET and Node.js flow through the same publish path with identical { capability, label } shape" }
      ]
    },
    {
      "version": "1.2.3",
      "date": "2026-05-09",
      "summary": "Reconciler.Nodejs multi-version support (parity with .NET reconciler).",
      "changes": [
        { "type": "feature", "text": "Reconciler.Nodejs 1.1.0 now discovers every Node.js install reachable on the host: PATH, $NVM_DIR/versions/node/v*/bin/node (Linux/macOS), %NVM_HOME%\\v*\\node.exe (Windows nvm-windows)" },
        { "type": "feature", "text": "Per-major capability publishing: runtime-nodejs-{major} flags + runtime.nodejs.{major}.version labels in NodeConfig. Cluster scheduler refuses to place an engines.node=20 app on a node that only has node 18" },
        { "type": "feature", "text": "Engine pin reading at deploy time: package.json engines.node → .nvmrc → .node-version. Fail-fast at deploy with actionable error if the required major isn't installed" },
        { "type": "feature", "text": "Per-install npm pinning — npm install runs against the SAME major's npm, avoiding native-module mismatches when system npm differs from the chosen major" }
      ]
    },
    {
      "version": "1.2.2",
      "date": "2026-05-09",
      "summary": "DatabaseManager Phase 3: developer grants, auto-expiry sweeper, drift detection, query workbench.",
      "changes": [
        { "type": "feature", "text": "POST /api/databases/servers/{id}/grants/developers — provisions a per-user SQL credential via the manager, stores password as managed secret, returns plaintext + connection string once" },
        { "type": "feature", "text": "Auto-expiry sweeper background service revokes + drops users for grants past their ExpiresAt every 5 minutes" },
        { "type": "feature", "text": "Drift detection background service compares cluster grant records against live SQL users every 30 minutes — finds orphan grants, orphan users, and expired-but-live cases" },
        { "type": "feature", "text": "Query workbench: in-browser SQL editor with database picker, result grid, NULL formatting, truncation indicator. IDatabaseProvider.ExecuteQueryAsync implementations for Postgres / MySQL / MSSQL" },
        { "type": "feature", "text": "63 unit tests covering Authorize() decision matrix, IdentifierGuard, drift classifier, developer username composition + 16 integration tests for /api/databases/* lifecycle, cascade, failure modes" }
      ]
    },
    {
      "version": "1.2.1",
      "date": "2026-05-09",
      "summary": "DatabaseManager Phase 2: cluster-side REST endpoints + GUI + auto-provisioning on app deploy.",
      "changes": [
        { "type": "feature", "text": "Cluster brokers per-app and per-developer credentials against EXISTING external Postgres / MySQL / MSSQL servers — does NOT host the engines, points at ones operators already run (RDS, Azure SQL, on-prem)" },
        { "type": "feature", "text": "DesiredDeployment.DatabaseRefs — apps declare their database needs in YAML; the reconciler auto-provisions a SQL user + grant + connection string env var before pod start" },
        { "type": "feature", "text": "GUI page: server registration, managed databases catalog, app + developer grant tables, register-server modal" },
        { "type": "feature", "text": "DatabaseProvider.Postgres / Mysql / Mssql provider modules using the same /api/provider/* pattern as DNS / SSL / LB" }
      ]
    },
    {
      "version": "1.2.0",
      "date": "2026-04-24",
      "summary": "Docker workloads, container registry, and load-balancer manager / provider split.",
      "changes": [
        { "type": "feature", "text": "Reconciler.Docker module — run Docker containers alongside native .NET processes" },
        { "type": "feature", "text": "Registry module — connect to any registry:2-compatible container registry, manage credentials, resolve image digests" },
        { "type": "feature", "text": "LoadBalancers module — unified load balancer manager that delegates to provider modules" },
        { "type": "improvement", "text": "LbNginx and LbHaproxy refactored into providers for LoadBalancers, using the same /api/provider/* pattern as DNS/SSL" },
        { "type": "feature", "text": "YARP route templates + per-host section overrides (RouteTemplateEndpoints)" },
        { "type": "improvement", "text": "Multi-replica per node with dynamic port allocation on Linux; runtime port now surfaced in ActualAppState" },
        { "type": "fix", "text": "Cross-node artifact distribution and cluster-internal auth" },
        { "type": "fix", "text": "IsolationManager plugin no longer crashes on load when ILoggerFactory is unavailable" }
      ]
    },
    {
      "version": "1.1.0",
      "date": "2026-04-15",
      "summary": "Custom leader/follower state replicator replaces Raft.",
      "changes": [
        { "type": "feature", "text": "Raft (DotNext.Net.Cluster) replaced with a custom leader/follower state replicator — SSE-streamed changes and per-node SQLite snapshots" },
        { "type": "feature", "text": "Per-node bootstrap of Reconciler.Dotnet so every node has a .NET reconciler on first run" },
        { "type": "improvement", "text": "Simpler break-glass recovery — ClusterManager.ReduceToSelf / RemoveMember for quorum loss" }
      ]
    },
    {
      "version": "1.0.4",
      "date": "2026-04-10",
      "summary": "Metrics collector improvements and bug fixes.",
      "changes": [
        { "type": "improvement", "text": "MetricsCollector module updated to v1.0.4 with improved collection performance" },
        { "type": "fix", "text": "Fixed metrics reporting interval drift on long-running nodes" }
      ]
    },
    {
      "version": "1.0.1",
      "date": "2026-04-05",
      "summary": "Module updates and stability improvements.",
      "changes": [
        { "type": "improvement", "text": "All modules updated to v1.0.1" },
        { "type": "fix", "text": "Fixed navigation issues in the web dashboard" },
        { "type": "improvement", "text": "Module versioning system introduced" }
      ]
    },
    {
      "version": "1.0.0",
      "date": "2026-04-01",
      "summary": "Initial release of AppCluster.",
      "changes": [
        { "type": "feature", "text": "Leader/follower cluster formation with automatic leader election" },
        { "type": "feature", "text": "5 hot-reloadable core plugins (ArtifactStore, ProcessManager, IsolationManager, ResourceManager, HealthReporter)" },
        { "type": "feature", "text": "Optional modules for DNS, SSL, jobs, webhooks, metrics, and load balancing" },
        { "type": "feature", "text": "REST API with full RBAC (Viewer, Operator, Admin roles)" },
        { "type": "feature", "text": "Declarative YAML/JSON configuration via CLI (validate, diff, apply, export)" },
        { "type": "feature", "text": "OS-level isolation: dedicated users, AppArmor (Linux), NTFS ACLs (Windows)" },
        { "type": "feature", "text": "Resource limits via cgroups v2 (Linux) and Job Objects (Windows)" },
        { "type": "feature", "text": "YARP reverse proxy with dynamic route configuration" },
        { "type": "feature", "text": "AES-256-GCM encrypted secrets management" },
        { "type": "feature", "text": "HTTP and TCP health checks with crash restart and exponential backoff" },
        { "type": "feature", "text": "Web dashboard for cluster management" },
        { "type": "feature", "text": "Audit log and webhook notifications" }
      ]
    }
  ]
}
