Skip to content

Changelog

All notable changes to Chatalot are documented here. Versions follow Semantic Versioning.


v0.25.22 — 2026-05-30

Secure self-service bot provisioning (HIVE-105 Parts 1+2) + the angle-bracket render fix (CHAT-181). Ships on top of the 0.25.21 Integrations work (below), which prod had not yet received — so a prod upgrade from 0.25.20 to 0.25.22 includes everything in both entries.

Added

  • HIVE-105 Part 2POST /api/admin/bots/bootstrap-token: deploy-secret-gated mint of the initial bot:provision token with NO human admin JWT. Gated by PROVISION_BOOTSTRAP_SECRET (X-Provision-Bootstrap-Secret header, constant-time SHA-256 compare), mounted outside the JWT middleware, 404 when the secret is unset, finds-or-creates the integration bot (is_admin=false), mints bot:provision only (never default/admin), idempotent.

Changed

  • HIVE-105 Part 1 — the community-join surface (add_bot_community, list_bot_communities, list_admin_communities) now accepts a bot:provision token or an admin JWT via require_provision_scope_or_admin. A bot:provision caller may add the bot as member role only. delete_bot, remove_bot_community, revoke_token stay admin-only.
  • CHAT-181 — the message markdown renderer escapes raw-HTML tokens to literal text (renderer.html), so <foo>, Map<String, Int>, comparisons, &, and quotes render intact; inline/fenced code keeps angle brackets without double-escaping; emoji + @mentions still render; user-typed raw HTML is no longer interpreted as markup. Renderer extracted to src/lib/markdown.ts.

Database

  • No new migration. Migration 064_bot_token_scope_provision.sql (introduced in 0.25.21) applies on first start if the prod DB hasn't seen it — additive bot_tokens.scope CHECK widening, existing rows untouched.

Tests

  • 6 new server unit tests (HIVE-105: member-only role limit, constant-time secret compare, bootstrap idempotency); 15 web render tests (CHAT-181, vitest+jsdom); crypto byte round-trip. Server suite 123/123.

Dev-prove

  • HIVE-105 Parts 1+2 + CHAT-181 dev-proven on the dev instance — see .qa-proof/HIVE-105.md and .qa-proof/CHAT-181.md.

v0.25.21 — 2026-05-29

Operator-friendly Integrations surface. Replaces "F12 → Application → Local Storage → copy chatalot_access_token → paste into Hivemind Connect" with a one-click wizard in the admin UI that mints a scoped token whose blast radius is bounded by the scope itself.

Added

  • CHAT-62b4cd3a — new admin Integrations tab. Wizard auto-creates a per-integration bot user (named integration-<sanitized-label>) AND mints a scoped token in one POST. Plaintext shown once. List view shows every active or expired non-default-scope token across all bots — the "what's connected to this chatalot" view operators have asked for. Inline revoke.
  • New bot-token scope bot:provision — grants POST /admin/bots, GET /admin/bots, GET /admin/bots/{id}, and POST /admin/bots/{id}/tokens. Cannot send messages, manage communities, suspend users, delete bots, or revoke other tokens. Intended for installer-style consumers like Hivemind Connect.
  • POST /api/admin/integrations and GET /api/admin/integrations admin routes (JWT-only — a scoped token cannot bootstrap more scoped tokens).

Changed

  • The four bot-CRUD admin routes (POST /admin/bots, GET /admin/bots, GET /admin/bots/{id}, POST /admin/bots/{id}/tokens) now accept either an admin JWT (today's path) OR a bot:provision-scoped bot token. DELETE /admin/bots/{id} and POST /admin/bots/{id}/tokens/{tid}/revoke stay JWT-only so a captured bot:provision token can't clean up its own tracks.

Database

  • Migration 064_bot_token_scope_provision.sql — extends the bot_tokens.scope CHECK constraint to allow bot:provision. Existing rows untouched.

Docs

  • New ADR adr-003-chatalot-scoped-bot-tokens-vocabulary locks scope naming convention, catalog, and the default-vs-scoped compatibility rule.
  • The Chatalot service docs gain an "Integration tokens" subsection.
  • docs/integrations/ai-workspace-setup.md gets an Integration tokens path for installer-style consumers.
  • Phase 2 (CHAT-cf361bae) still adds the full scope catalog + middleware route-to-scope map. This work locks the vocabulary so Phase 2's map keys are stable.

v0.25.20 — 2026-05-29

UI-only follow-up to 0.25.19 — surface the bot-token scope dropdown on the admin Bots mint modal so the operator doesn't have to curl just to mint a metrics-scoped token.

Added

  • CHAT-966a0305 — admin Bots tab mint modal: new "Scope" <select> with two options (default — full bot permissions; metrics — read-only Vantage cockpit endpoint only) and a one-line consequence explainer that updates with the selection.
  • Tokens table on the bot detail panel gains a "Scope" column. metrics tokens render as a purple chip so an operator instantly knows they don't reach the messaging API.

Changed

  • API client (mintBotToken) accepts an optional scope; omits the field when undefined to preserve the backend's default allow-list value.
  • TokenSummary and MintTokenResponse TS interfaces expose scope (backend already shipped both fields in 0.25.19).

Database

  • None. No migration, no schema change. Behavior unchanged for existing tokens — they all remain scope=default.
  • Backstop for CHAT-cf361bae (Phase 2 still ships the full scope catalog + middleware route-to-scope map).

v0.25.19 — 2026-05-29

Vantage cockpit producer wiring + admin Bots UX cleanup. Both verified end-to-end on the dev instance before cutting.

Added

  • CHAT-174 — Vantage cockpit metadata-only metrics endpoint (GET /api/v1/metrics/vantage). Returns instance version + health + workspace / member / channel / message counts. Zero message content, no PII, aggregate counts only — E2E encryption on chat messages is unaffected and intact even if a poller token is exposed.
  • Bot-token scopes (Phase 1, CHAT-174): new bot_tokens.scope column with allow-list (default, metrics). POST /api/admin/bots/{id}/tokens accepts an optional scope; default behavior unchanged for existing tokens. The metrics endpoint requires scope=metrics; JWT humans and default-scope bot tokens (even admin) get 403.
  • CHAT-3f242d36 — Admin → Bots row gains a "Communities" column with a per-bot chip: yellow "No communities" for unattached bots (the typical post-Connect state), neutral count chip otherwise.

Changed

  • CHAT-3f242d36 — Admin → Bots row button "Tokens" → "Manage". The panel it opens has held tokens + communities + bio + lifecycle for a while; the old label hid the door. No behavior change, label only.

Database

  • Migration 063_bot_token_scope.sqlALTER TABLE bot_tokens ADD COLUMN scope TEXT NOT NULL DEFAULT 'default' with CHECK (scope IN ('default','metrics')). Catalog-only write on Postgres ≥ 11; safe on a populated table. Existing tokens default to 'default', behavior unchanged.
  • CHAT-cf361bae (filed) — Phase 2 follow-up: full scope catalog (webhook, relay, …) + middleware route-to-scope map so default-deny replaces per-handler opt-in. Not blocking.

v0.25.18 — 2026-05-25

Bot-as-client E2E foundations (ADR-002 Option B). Enabling work so an external bot crypto client (built in Hivemind) can participate in end-to-end-encrypted channels as a real member; the server never holds bot private keys.

Added

  • CHAT-162 — the WebSocket Authenticate frame now accepts a bot's X-Bot-Token (cb_…) in addition to a JWT, routed through the same shared bot-auth as the HTTP API (active/unrevoked/unexpired token, is_bot, not suspended, rate-limit, suspended_users). Authentication only — authorization is unchanged (a non-member bot still cannot send to or read a channel).
  • CHAT-164docs/developer-guide/bot-e2e-client.md: the authoritative chatalot-side contract a bot crypto client implements (key registration, X3DH, sender-key lifecycle, WS send/receive + rotation events, wire-format notes).

Changed

  • CHAT-163 — removing an E2E-capable bot (one that has registered an identity key) from a community now rotates the affected channels' sender keys for forward secrecy, reusing the existing leave/kick rotation path. Keyless bots keep the cheap path.

Verified

  • CHAT-165 — confirmed an is_bot user can register keys, replenish prekeys, fetch its bundle, and post/get sender keys via X-Bot-Token, with channel membership still enforced (non-member → 403).

v0.25.17 — 2026-05-25

Added

  • Scheduled & recurring channel meetings (EPIC CHAT-155). A Meetings panel on each channel lets moderators+ schedule one-off or recurring meetings (RFC-5545 RRULE), with DST-correct recurrence and a shareable join link that deep-links back into the channel.
  • CHAT-156 — backend: scheduled_meetings model + CRUD API + recurrence expansion (migration 061).
  • CHAT-157 — frontend: channel Meetings UI (schedule / list / edit / join / link); the meeting "room" is the existing voice channel.
  • CHAT-158 — RSVP (going / maybe / declined), reminders before start via a background sweeper (WebSocket + offline web-push), and notify-on-schedule-change (migration 062).
  • CHAT-159 — iCalendar (.ics) export per meeting for Google / Outlook / Apple (recurring meetings included).
  • Add a bot to a community/channel (CHAT-160, part of CHAT-10). Instance admins can add a bot account to a community at a role from the Bots tab; the bot auto-joins the community's public channels and becomes a real member (manage via the API, post via webhooks). Reading/sending E2E message content as a bot is a separate effort (CHAT-161, ADR pending).

v0.25.16 — 2026-05-24

Fixed

  • Community admins/owners couldn't delete webhook or others' messages — client side (CHAT-148, follow-up). The server authz was fixed in 0.25.13, but the client gated the "Delete (mod)" action on the channel role (myRole) alone, so an admin, owner, or moderator who didn't create the channel (channel role member) never saw the option even though the server would allow the delete. A new canModMessages() helper now also allows instance admin/owner and community owner/admin/moderator, mirroring the server. Verified in-browser: a community admin (not instance admin, not channel creator) sees and uses "Delete (mod)" successfully.
  • "What's New" popup showed an old version (0.23.0) after updating (CHAT-149 family). The in-app changelog array was frozen at 0.23.0, and the dialog fell back to the latest array entry when no entry matched the running version. Added a current entry and changed the trigger to only auto-show when there's a curated entry for the running version — it no longer presents an older version as "what's new."

v0.25.15 — 2026-05-24

Fixed

  • Webhook messages rendered as "Deleted User" with no body on history load (CHAT-147, follow-up). The 0.25.13 fix only patched the live WebSocket broadcast; create_webhook_message stores the webhook content in the plaintext column with a \x00 ciphertext placeholder, and the message read path (GET /channels/{id}/messages, threads) returned that placeholder and dropped plaintext. So on any reload or scrollback — i.e. essentially always — webhook messages showed "Deleted User" with an empty body. The read path now serves the webhook plaintext in the ciphertext field, consistent with the live broadcast, so the client renders the webhook name, avatar, and text. This also repairs already-stored webhook messages (they all have plaintext populated). Caught by the release-lifecycle functional test (browser render check), which health/artifact checks missed. Known gap: pinned webhook messages use a separate model without plaintext (rare).

v0.25.14 — 2026-05-24

Fixed

  • Web-client auto-update was silently disabled; new frontends never reached already-loaded clients (CHAT-149). Both the client's baked __APP_VERSION__ and the server-advertised client version (static/version.json, sent in the WebSocket Authenticated message) are derived from clients/web/package.json, which had not been bumped since 0.25.10. With both sides reading 0.25.10, the client's version-mismatch check (server_version !== __APP_VERSION__) never fired, so connected and cached clients never reloaded to pick up new frontend code — including the 0.25.13 webhook render fix (CHAT-147), which shipped in the image but stayed invisible to loaded clients (webhook messages still showed "Deleted User"). package.json (and its lockfile) is now bumped in lockstep with the workspace version so the update signal moves each release; clients on an older build now detect the mismatch and silently reload into the new bundle. No server-code changes from 0.25.13. Follow-up: teach the release ceremony to bump package.json automatically so this can't recur (CHAT-149).

v0.25.13 — 2026-05-24

Fixed

  • Webhook messages displayed as "Deleted User" with a raw JSON body (CHAT-147). Real-time webhook broadcasts carried a placeholder ciphertext, so the client fell back to rendering the stored JSON payload and resolved the (null) sender to "Deleted User". Webhook messages now show the configured webhook name and avatar (or the per-message username override) and render the message text, matching how they already appeared in loaded history. The plaintext indicator was softened from "Webhook · not E2EE" to a quieter "via webhook" badge; the not-end-to-end-encrypted detail remains in its tooltip.
  • Community owners and admins could not moderate webhook (or other) messages (CHAT-148). Message deletion, pin/unpin, kick, ban, unban, and voice-kick resolved the actor's role from channel_members, so community owners and admins — whose role lives in community_members, not per channel — resolved to plain members and were denied. All channel-scoped moderation now resolves the community role through a shared get_community_role_for_channel helper, with instance owner/admin still promoted as before.

v0.25.12 — 2026-05-24

Fixed

  • Webhook management returned 403 for community owners and admins (CHAT-145). Webhook create/list/update/delete authorized against channel_members with an owner-only check, so legitimate community owners and admins (and instance admins) were denied — blocking all webhook-based integrations (CI, alerts, announcements). Authorization now resolves the channel's community and allows owners or admins (and instance admin/owner), matching the documented policy ("Owner or Admin of the channel's community").

v0.25.11 — 2026-05-23

Fixed

  • Self-hosting TURN docs corrected to the ephemeral scheme (CHAT-135). docs/self-hosting/docker-deployment.md showed coturn using static --user=...:... long-term credentials, and configuration.md's .env template still listed TURN_USER/TURN_PASSWORD/ICE_SERVERS — both contradicted the v0.24.0+ ephemeral HMAC scheme (TURN_AUTH_SECRET + TURN_URLS, coturn --use-auth-secret). Following the stale bits left TURN unconfigured, so /api/account/turn-credentials returned 503, voice silently fell back to STUN-only, and relay-dependent peers had no audio. Added a "no audio" voice section to the self-hosting troubleshooting guide.

Changed

  • TURN misconfiguration is no longer silent (CHAT-135). The server logs a startup WARN when it detects an identifiable TURN misconfig (deprecated TURN_USER/TURN_PASSWORD set while the ephemeral pair is unset, or TURN_AUTH_SECRET/TURN_URLS half-configured) and logs the specific reason (DEBUG) when /api/account/turn-credentials returns 503. The web client now emits a console warning when it falls back to STUN-only on a 503 instead of swallowing it. Reported by AIWF; the participant-panel discrepancy they saw was confirmed downstream of the dead media path, not a separate roster bug.

v0.25.10 — 2026-05-09

Added

  • Bot accounts and long-lived API tokens (CHAT-10). Operators can now create headless "bot" users from the Admin → Bots tab. Each bot is a regular users row with is_bot = true; it has no password and no client identity keys. Bots authenticate to the API via the X-Bot-Token: cb_<64 hex> header, never JWT.

  • 6 admin endpoints under /api/admin/bots: list, create, get, delete, mint token, revoke token.

  • Token plaintext is shown to the operator EXACTLY ONCE at mint time. The DB stores only the SHA-256 hash and an 11-char prefix for UI lookup.
  • Tokens carry an optional expiry (hours from now, or never) and a free-form label.
  • unified_auth_middleware tries X-Bot-Token first, falls back to JWT Bearer. Mixed auth (both headers) is rejected to avoid identity ambiguity.
  • Per-bot-token rate limiting: default 60 requests / 10 second window. Exceeding the budget returns HTTP 429 with code: "rate_limited". Override per-token via bot_tokens.rate_limit_per_window.
  • Persistent (DB-backed) — survives container restart, mirroring the webhook rate-limit pattern.
  • Admin UI: new Admin → Bots tab. Lists bot accounts, creates new bots, mints / revokes tokens, displays the one-time-show plaintext with a copy button.

Bots are otherwise normal users — they appear in member lists, can be added to communities/channels via the usual flows, can be made community/group admins, and respect existing permissions. Intended consumer is docs/integrations/ai-workspace-setup.md.

Operator notes

  • Migration 059_bot_accounts.sql adds users.is_bot and creates the bot_tokens table.
  • Migration 060_bot_token_rate_limit.sql adds the bot_token_rate_limit window-tracking table and the rate_limit_per_window override column on bot_tokens.
  • No public API surface change for human users. Existing JWT-Bearer flow is unaffected.

v0.25.9 — 2026-04-26

Security

  • Community assets, group assets, and custom emojis are now authenticated and membership-gated (CHAT-116). Previously the /community-assets/{filename}, /group-assets/{filename}, and /emojis/{id} routes were registered as public — anyone with a URL could pull the file. For private workspaces with confidential branding (customer logos, internal channel emojis, voice-call backgrounds), the URL leaked via referrer headers, browser history, shares, or screenshots.

The routes now sit behind auth_middleware AND each handler verifies membership before serving: - Community icon / banner: parses <community_uuid>_<role>.<ext> from the filename and requires is_community_member. - Custom emoji: looks up community_id on the emoji record and requires is_community_member. - Group icon / banner: parses <group_uuid>_<role>.<ext>, resolves to the parent community, requires is_community_member. - Voice background: parses <channel_uuid>_voicebg.<ext> and requires channel_repo::is_member (channel-level membership). - Instance admins / owners always pass. - Non-members get 404, never 403, so the existence of an asset is not disclosed.

Cache headers flipped from public, max-age=… to private, max-age=… so shared caches (CDNs, proxies) don't serve one user's response to another.

Added

  • Service worker now intercepts gated asset fetches and attaches the Authorization: Bearer <token> header automatically. Required because <img> / <style url(...)> / <audio> can't set headers. The auth store posts the access token to the SW on login, refresh, and logout via postMessage. Token lives in SW memory only — never persisted, never reachable from page-context JavaScript.

Operator notes

  • Existing community/group/emoji asset URLs continue to work for members. Non-members previously got HTTP 200; they now get 404.
  • A logged-out browser visiting a stale image URL (e.g. cached preview link) will get 401 / 404. The page calling the URL must be authenticated for assets to load.

v0.25.8 — 2026-04-26

Privacy

  • Feedback now lands in a local table first (CHAT-124) — POST /api/feedback writes a row to a new feedback_submissions table before any external fan-out. The operator's database is the source of truth; mirroring to GitHub Issues / Forgejo Issues is optional, best-effort, and fire-and-forget. Closes the cross-tenant leak risk where a managed-customer's feedback (potentially including UI screenshots) was posted to a vendor GitHub repo.

Added

  • Forgejo Issues fan-out for feedback — when FEEDBACK_FORGEJO_BASE_URL + FEEDBACK_FORGEJO_REPO + FEEDBACK_FORGEJO_TOKEN are set, every feedback submission is mirrored as an issue on the configured Forgejo repo with an instance:<host> label so operators triaging cross-instance can filter by source. Forgejo's API is GitHub-compatible (POST /api/v1/repos/{owner}/{repo}/issues).
  • Admin "Feedback" tab in the admin panel — lists submissions with status filter (new / triaged / closed / all), per-row expand with description + screenshot link, and Mark new / Mark triaged / Mark closed actions. Stat cards across the top.
  • GET /api/admin/feedback?status= — paginated queue list with per-status counts.
  • PATCH /api/admin/feedback/{id} — update status. Audit trail via triaged_at + triaged_by.
  • GET /api/admin/feedback/{id}/screenshot — serves stored screenshot bytes with the original MIME type.

Deprecated

  • GITHUB_API_TOKEN + GITHUB_REPO_OWNER + GITHUB_REPO_NAME env forwarding remains for backward compatibility with self-hosters who already wired it, but is no longer the default. New Seglamater-managed instances should use the Forgejo path (or local-only).

Migration

  • 058_feedback_submissions.sql — new table with screenshot blob, status enum, status+created_at index for queue queries, plus forwarded_to TEXT[] to record successful fan-out targets.

Operator notes

  • chat.seglamater.app and a staging instance: remove GITHUB_API_TOKEN / GITHUB_REPO_* env vars after applying v0.25.8 to fully sever the GitHub forwarding path. Local table
  • admin Feedback tab is the new triage surface.

v0.25.7 — 2026-04-26

Security

  • WebSocket subscription drained on kick / leave / ban (CHAT-117) — the per-channel broadcast forwarder spawned by Subscribe checked membership at subscribe time but not on each forwarded event. A user whose membership was revoked mid-session continued receiving ciphertext + metadata until they disconnected. The forwarder now consults a per-(user, channel) revocation set on every event; kick, leave, and ban paths set the flag immediately and the forwarder drops the subscription on the next message. Set is cleared on legitimate re-subscribe so a re-invited user works normally.
  • Community ban now rotates sender keys — sister bug to CHAT-115 (shipped v0.25.6 critical). The community ban path removed membership rows but never deleted the banned user's sender-key distributions or asked remaining members to rotate. The banned member retained chain-key seeds and could decrypt future ciphertext on those chains. Now mirrors the kick/leave flow.
  • Voluntary channel leave now rotates sender keys — same class of bug, smaller surface. Self-leaving a channel via POST /api/channels/{id}/leave now deletes the leaver's sender-key distribution and broadcasts SenderKeyRotationRequired.

Improved

  • Communities default to discoverable = FALSE (CHAT-118) — new workspaces are private-by-default. The invite-info endpoint redacts community name + description for non-discoverable workspaces, so a freshly-created internal/customer workspace no longer leaks its identity to invite holders. Existing rows are not touched; flip via PATCH /api/communities/{id} {discoverable: true} if the workspace is meant to be public.

Added

  • Invites can pre-assign a community role (CHAT-123) — POST /api/communities/{cid}/invites now accepts grants_role: "moderator" | "admin". Authorization mirrors set_member_role: only owners can mint admin-grant invites, only managers (admin or owner) can mint moderator-grant invites. Role is applied immediately after join_community on accept, with an audit log entry (community_invite_role_grant). Removes the dual-step "user joins → operator promotes" dance.

Migrations

  • 056_discoverable_default_false.sql — flips column default for new rows; existing rows untouched.
  • 057_invite_grants_role.sql — adds community_invites.grants_role with check constraint.

v0.25.6 — 2026-04-26

Security

  • CRITICAL: community kick / leave now rotates sender keys — the channel-level kick path correctly deleted the leaving user's sender-key distributions and broadcast SenderKeyRotationRequired to remaining members, but the community-level kick (and voluntary leave) skipped both steps. The leaving member retained chain-key seeds and could decrypt future ciphertext on those chains. Now both paths enumerate the community's channels and run the same rotation flow as a per-channel kick. Mirrors routes/channels.rs:402-410. Found by workspace-isolation audit pre-multi-tenant rollout.

Fixed

  • community_repo::search_visible_users — ILIKE wildcard escape applied. User-supplied search queries containing literal %, _, or \ no longer expand as wildcards. Same pattern as user_repo::search_users.

v0.25.5 — 2026-04-26

Added

  • Admin Updates: "Test connection" button (CHAT-106) — round-trips a signed request to the chatalot-updater sidecar and reports pass/fail with latency. Catches install-time misconfigs (env passthrough, secret mounts, network) before the operator hits Apply and the bug surfaces under the disruption window. Suggested by AIWF after CHAT-105's env-passthrough bug bit them.
  • Admin Updates: in-flight apply state restoration (CHAT-113) — navigating away from the Updates tab and back no longer loses the apply progress panel. On tab mount, the UI now queries the updater for the most recent apply; if it's non-terminal, the progress panel re-attaches and polling resumes. Combined with humanized progress states from v0.25.3, the apply UX now survives all the typical ways an admin's browser session interacts with a long-running apply (idle nav, mid-apply refresh, secondary tab open).
  • OIDC: auto-promote user matching ADMIN_USERNAME on first login (CHAT-115) — for SSO-only deployments where the operator never had a local account, the OIDC register path now grants is_admin = true to the first OIDC user whose username claim matches the configured ADMIN_USERNAME env var. Removes the dual-account dance the operator hit when bringing up SSO on a staging instance.

v0.25.4 — 2026-04-26

Improved

  • Admin Updates: image digest visible in both Running and Latest cards — when the running version matches the latest channel manifest, the Running-now card now shows the digest of the running image (helpful for cross-instance verification on the same version). The Latest- available card shows the digest of the release that's about to be installed. Full digest is in a collapsed <details> for copying; the short prefix is shown by default to keep the card compact.
  • Admin Updates: prominent flag bannersbreaking_changes and security_advisory flags from the manifest now render as full-width banners with icons, hoisted ABOVE the version display so they can't be missed. Previously they were thin inline notes below the version, which the operator-as-customer scrolled past on the v0.25.0 apply test.

v0.25.3 — 2026-04-26

Improved

  • Admin Updates: humanized progress states (CHAT-110) — the apply progress panel previously displayed raw JSON like State: {"state": "broadcasting_maintenance"}. The panel now shows a friendly label (e.g. "Notifying connected users"), a one-line description of what the orchestrator is doing, and a phase tag (Pre-flight / Active swap / Verifying / Rollback) so admins can see at a glance whether their instance is still in safe-to-abort territory or already in the disruption window. Each of the 12 in-progress orchestrator states has its own label + description.

v0.25.2 — 2026-04-26

Improved

  • Admin Updates: stylized apply confirmation dialog — replaces the browser-default confirm() popup with the same Chatalot-themed modal used elsewhere in the admin panel. The confirmation now shows the current and target versions explicitly and a one-line note about the pre-disruption snapshot + automatic rollback.
  • Admin Updates: clearer post-apply success state — after a successful apply, the panel now surfaces the new version explicitly, explains that the browser is still running the previous build's JavaScript, and offers a one-click "Reload page" button to pick up the new build. Previously the only feedback was a brief one-line "Applied successfully" with no call-to-action.

v0.25.1 — 2026-04-26

Fixed

  • Admin Updates UI: "Waiting for first state update..." hang (CHAT-108) — chatalot-server's /v1/apply/:id deserializer required the field apply_id, but chatalot-updater's ApplyRecordJson serializes it as id. Server returned 500 to its own UI on every poll, so the apply progress panel never advanced even when the apply was completing cleanly. Server now accepts both id and apply_id, and both current_state and state, via serde rename + alias. Regression test (parses_sidecar_v1_apply_response_shape) locks the contract.
  • First Apply fails on snapshot — chatalot_backups volume owned by root (CHAT-107) — the chatalot-updater image now runs as root long enough for an entrypoint script to chown /var/backups/chatalot to the updater uid (1001), then drops to updater via gosu and execs the binary. Required because compose-created volumes default to root:root 755, which the binary couldn't write into. Existing customers with the chown workaround in place can either continue with that or pull the new updater image (manual recipe: docker compose pull chatalot-updater && docker compose up -d chatalot-updater). New bundle installs pick this up automatically.

Notes

  • Wire-format of the /v1/apply/:id response did not change. The drift was always in the server's deserializer; v0.25.1 makes the server tolerant. No client-side action needed beyond applying.

v0.25.0 — 2026-04-25

Security

  • X3DH AD now bound into Double Ratchet AAD — the X3DH-derived associated data (IK_A || IK_B) is now included in the AAD passed to ChaCha20-Poly1305 for every Double Ratchet encrypt/decrypt, matching the Signal Double Ratchet spec §3.4 (ENCRYPT(mk, plaintext, CONCAT(AD, header))). This closes a subtle gap where a server-MITM substituting an identity key after the X3DH handshake would not have caused first-message decrypt to fail. AD was already computed correctly in x3dh.rs but was discarded before reaching the ratchet AAD.

Breaking

  • Wire-format AAD change invalidates existing DM sessions. Clients upgrading to 0.25.0 cannot decrypt messages from sessions established against pre-0.25.0 peers. All DM sessions automatically re-handshake via X3DH on next message — users will see a one-time "identity-key change" notice (TOFU) the first time they message a peer post-upgrade unless their peer also upgraded.

v0.24.6 — 2026-04-24

Fixed

  • Admin "Updates" tab: 401 on every request — the new tab's fetch calls used raw fetch() with credentials: 'same-origin' and relied on cookies; chatalot-server authenticates via Authorization: Bearer <token> (pattern established for every other admin API call). Refactored to use the shared api.get / api.post helpers from $lib/api/client so the Bearer header is attached automatically. Caught by the operator on first real admin-UI visit after v0.24.5 deployed.

v0.24.5 — 2026-04-24

Added

  • Admin "Updates" UI (CHAT-Phase-2c) — new tab in the admin panel showing the currently-running version, the latest available release on the configured channel, and an Apply button that HMAC-signs a request to the local chatalot-updater sidecar. Progress panel surfaces the apply state machine (succeeded / failed / rolled_back / recovered / frozen) via 3-second polling, tolerant of the brief 502 window while the server itself swaps. Breaking-change and security-advisory banners are rendered from the manifest fields. Release-notes link is pulled from the manifest's release_notes_url.
  • /api/admin/updates/{status,check,apply,apply/:id} endpoints — admin-only, with audit-log entries (update_applied) for every approved apply. Target-version is verified against the cached channel head before signing a sidecar request; typed-by-hand versions are rejected.
  • services/updater_client — HMAC-SHA256 signer for /v1/apply and /v1/apply/:id, canonical format locked to the sidecar's wire format via a reference-vector unit test (so a drift on either side fails a build rather than silently 401ing at apply time).

Config

  • UPDATER_API_URL — URL of the local chatalot-updater sidecar (default http://chatalot-updater:8081 via compose).
  • UPDATER_API_TOKEN_FILE — mounted secret path; direct UPDATER_API_TOKEN env also accepted. Falls back to /run/secrets/updater_token if neither is set.
  • UPDATE_CHANNEL — canary|beta|stable (default stable).
  • UPDATE_MANIFEST_HOST — base URL for channel-pointer fetches; default https://updates.seglamater.app. Override for self-hosted release channels.

Deferred (follow-ups)

  • In-process manifest signature verification will land paired with the sidecar's wave-2 cosign work.
  • Periodic background poll + "update available" nav badge — the tab currently loads on-demand.

v0.24.4 — 2026-04-24

Fixed

  • Managed-update recreate preserves compose secret-file mounts + security hardening — the orchestrator's container recreate was copying Binds but silently dropping HostConfig.Mounts (where docker compose delivers secrets: entries, e.g. /run/<omitted>/jwt_private_key), readonly_rootfs, security_opt, cap_add/cap_drop, and tmpfs. Net effect: the new container came up without its JWT / DB-password / TOTP-key files (ENOENT crashloop) AND without its hardening. Fix widens the allowlist in build_recreate_body to preserve all placement, mount, and security fields. Caught by the v0.24.3 smoke on a staging instance — the first end-to-end apply against a real registry image.

Smoke test milestone

First end-to-end managed-update apply on a staging instance succeeded after this fix: signed /v1/apply → manifest fetch from updates.seglamater.app → cosign-pinned pull from registry.seglamater.app → pre_flight → snapshot → migrate → swap → health pass. ~1 min apply time.


v0.24.3 — 2026-04-24

Fixed

  • Updater sidecar phase_pull now uses the pinned @digest ref (CHAT-58 follow-up) — with the bare repo path, bollard's create_image + the dockerd resolver occasionally landed on a cosign signature artifact tag (sha256-<prev-digest>) during tag-list lookups, which is a single-platform OCI artifact with no runnable linux/amd64 manifest. Switching to the full pinned ref bypasses tag-list resolution entirely: the daemon fetches the exact content-addressed manifest.
  • Ship scripts/updater/pre_flight.sh + scripts/updater/migrate.sh in the runtime image — the orchestrator execs these inside one-shot containers during an apply. They didn't exist in the repo previously, so the very first real apply died with exec: no such file or directory. Current stubs are exit 0; CHAT-59 tracks real implementations (binary sanity-check for pre_flight, schema migration for migrate).

v0.24.2 — 2026-04-24

Fixed

  • Updater sidecar now passes registry credentials on docker pull (CHAT-58) — the sidecar was making unauthenticated pulls and 401'ing against private registries (the production Seglamater one is a REQUIRE_SIGNIN_VIEW=true Forgejo). Added an optional CHATALOT_UPDATER_REGISTRY_CREDS_FILE env var (default /run/secrets/registry_creds). When the file is present with contents {"username":"…","password":"…","serveraddress":"…"}, credentials are forwarded to docker on every pull via X-Registry-Auth. When absent or whitespace-only, pulls stay anonymous — suitable for public registries and for installations that don't use managed updates. Malformed JSON is a hard startup error (better than silently falling back to anon and 401'ing at apply time). See secrets/registry_creds.example and the self-hosting configuration docs. Per CHAT-13 Decision #1, clients receive read-only per-instance tokens during onboarding; write-scoped tokens stay vendor-side.

Operator migration note

Instances running the updater compose profile need to provide secrets/registry_creds (or touch it for anonymous pulls) before docker compose up -d. The install.sh helper (tracked in Phase 2d) will handle this automatically; manual deployments should either cp secrets/registry_creds.example secrets/registry_creds and edit, or touch secrets/registry_creds to opt out of authenticated pulls.


v0.24.1 — 2026-04-24

Fixed

  • Updater sidecar pre_flight/migrate/start_new phases now use pinned image refs (CHAT-57) — the orchestrator was using manifest.image bare (no tag) at three phases that spin up or recreate containers. Docker resolves a bare ref as :latest, which doesn't exist in the canonical registry, so the sidecar failed with No such image: ...chatalot:latest as soon as a real (non-placeholder) manifest was applied. Added ReleaseManifest::pinned_ref() returning <image>@<image_digest> and switched the three affected phases to use it. Belt-and-suspenders: error-out if either field is missing at these phases rather than falling back to bare/empty. Discovered during the v0.24.0 canary smoke test on 2026-04-24.
  • Updater phase order: pull now precedes pre_flight (CHAT-57 ctd.) — bollard's container-create does not auto-pull, so pre_flight and migrate cannot run against an image that hasn't been pulled yet. Previously pull happened AFTER pre_flight; the bare-image bug masked this ordering issue. Moved phase_pull to position 3 (after verify_image + previous-image capture, before pre_flight). Still pre-disruption; a pull failure still triggers fail_clean with no rollback needed.

Release-pipeline internals (operator-only, not user-facing)

  • chatalot-release CLI: sign-blob via cosign 3.x --bundle format (deprecated --output-signature - broke cosign 3.0.6).
  • chatalot-release CLI: rsync --omit-dir-times so the deploy phase exits cleanly against the rrsync-wo target on the new updates.seglamater.app host.

v0.24.0 — 2026-04-24

Security

  • Webhook plaintext disclosure (CHAT-9) — webhook messages are stored in plaintext on the server (by design -- external integrations can't participate in per-user E2E encryption). The UI now distinguishes them with a yellow "Webhook · not E2EE" badge in the message header, the webhook creation form shows an explicit disclosure warning, and docs/user-guide/webhooks/using-webhooks.md opens with a Security & Privacy section. docs/self-hosting/security-hardening.md adds a "Message Encryption Coverage" table so operators understand which message types are E2E and which aren't. No behavior change for existing deployments -- webhook storage is unchanged; this is a disclosure pass.
  • Server-side message search removed (CHAT-8 Phase 1) — the legacy GET /api/channels/:id/messages/search and GET /api/messages/search routes, along with the underlying search_messages and search_messages_global repo functions, have been removed. They worked by running convert_from(ciphertext, 'UTF8') ILIKE $pattern against the messages table, which only functioned because messages were still stored as UTF-8 plaintext in the ciphertext column (a Phase 1 carry-over predating the full E2E rollout). That contradicted the end-to-end encryption claim. The client search UI is now disabled with a banner explaining the rebuild. Phase 2 will reintroduce search as a client-side Tantivy-WASM index that never touches the server (tracked separately).
  • Ephemeral TURN credentials (CHAT-15) — coturn is now configured with --use-auth-secret, a shared HMAC-SHA1 key between chatalot-server and coturn. The server issues short-lived credentials (default 1h TTL) via the new GET /api/account/turn-credentials endpoint, authenticated via JWT. Previously, one static TURN_USER/TURN_PASSWORD was shared by every user forever. New env vars: TURN_AUTH_SECRET (required when the turn compose profile is active), TURN_URLS (comma-separated list returned to clients), TURN_CREDENTIAL_TTL_SECS (60-86400, default 3600). The legacy ice_servers field on GET /api/auth/config remains for one release for back-compat.
  • TLS + DTLS on coturn (CHAT-14) — removed --no-tls --no-dtls, added a self-signed cert generated at first boot (stored in the turn_certs named volume; regenerate by deleting the volume). TURN now listens on TCP/5349 (TLS) and UDP/5349 (DTLS) in addition to plaintext 3478. WebRTC clients will prefer the encrypted transport.
  • Persistent login lockout (CHAT-16) — failed-login state is now stored in the login_attempts table (migration 053) instead of an in-memory DashMap. Lockouts now survive server restarts. Previously, restarting the container would reset the counter and let brute-force attempts resume immediately.
  • Fail-closed on missing TOTP_ENCRYPTION_KEY (CHAT-18) — the server now refuses to start when TOTP_ENCRYPTION_KEY is unset or empty. Previously it would log a warning and continue, silently encrypting TOTP secrets with a key derived from the empty string. Operators upgrading must set TOTP_ENCRYPTION_KEY in .env (or mount /run/secrets/totp_encryption_key). Generate with openssl rand -hex 32.
  • Pinned wasm-pack version (CHAT-17) — the Docker build now installs wasm-pack 0.13.1 via cargo install --locked instead of piping the upstream install script through sh. Reproducible, supply-chain-auditable.

Added

  • chatalot-updater crate — DB snapshot and restore primitives for the managed-update system (CHAT-17). New chatalot-snapshot CLI binary with subcommands create, list, restore, verify, prune. Snapshots are gzipped pg_dump output stored at /var/backups/chatalot/{timestamp}-{version}.sql.gz with a JSON sidecar manifest. Configurable via CHATALOT_SNAPSHOT_DIR (default /var/backups/chatalot), CHATALOT_SNAPSHOT_RETAIN_COUNT (default 5), CHATALOT_ROLLBACK_WINDOW_HOURS (default 24, max 168). Passwords pass via PGPASSWORD env var only — never on the command line. See docs/self-hosting/backup-and-restore.md for usage.
  • Admin audit log richness (CHAT-12) — every admin endpoint now records IP address (via trusted-proxy-aware extraction), user-agent (truncated to 512 bytes), and before/after state for each mutation. Action strings are now drawn from typed constants in chatalot-db::repos::audit_repo so there's one source of truth. Reserved action names for the upcoming managed-update system (update_applied, update_rolled_back, release_channel_changed, etc.) are defined alongside; they're wired up by the updater subsystem.

Changed

  • Configurable coturn listener ports (CHAT-44) — the coturn sidecar's listening ports are now driven by TURN_LISTENING_PORT (default 3478) and TURN_TLS_PORT (default 5349) instead of being hardcoded into the compose command. Default behavior is unchanged — admins who don't touch their .env get the IANA standard ports. Override when another service on the host already owns one of the defaults (most commonly a Tailscale/Headscale DERP relay using UDP/3478). If you override, remember to update TURN_URLS and your firewall rules to match. Port-conflict guidance added to docs/self-hosting/configuration.md.
  • Release-manifest host migrated to updates.seglamater.app (CHAT-41) — the canonical location for Chatalot's signed release manifests and the cosign public key is published at updates.seglamater.app. Self-hosting clients running v0.24.0+ poll the new URL; the public key at the new location is the same material (SHA-256 f1ff3f7ff3a1a7fe53620bbd8db0e362e55dcf622780e271740bc27d1850d082). No operator action needed for existing deployments — the updater sidecar reads the URL from its configured build, so upgrading to 0.24.0 automatically moves clients to the new host. The signed manifests are published from the seglamater/chatalot-updates repo.

Added

  • chatalot-release CLI (CHAT-Phase-2a) — operator-only binary at crates/chatalot-release that automates the six-phase release ceremony (preflight → git tag → docker build + push → cosign sign + verify → manifest generate + sign-blob → channel promote + rsync deploy + post-deploy verify). Not included in the runtime image; invoked on the operator workstation during a human-supervised release. Dry-run mode prints the full plan without touching state. See docs/developer-guide/releasing.md for usage.

v0.23.0

Added

  • OIDC/SSO support — login via Authentik, Keycloak, or any OpenID Connect provider
  • OIDC user provisioning (auto-create accounts on first SSO login)
  • Option to disable password login when OIDC is enabled (OIDC_DISABLE_PASSWORD_LOGIN)
  • CI workflow for automated testing on push/PR (cargo check, clippy, frontend build)
  • Root-level SECURITY.md, CONTRIBUTING.md, and CHANGELOG.md
  • TURN/coturn documentation in self-hosting guide
  • Build hash cache-busting for static assets

Fixed

  • CSP no longer hardcodes specific domains — WebSocket origins derived from PUBLIC_URL
  • CORS fallback no longer leaks author's domain — derived from PUBLIC_URL
  • Fixed og:image using absolute URL (now relative)
  • Version sync across all manifests (workspace, desktop, frontend)

Security

  • Removed internal homelab hostnames from CSP headers
  • Added responsible disclosure policy (SECURITY.md)

v0.22.6

Added

  • UI zoom (50–200%) with keyboard shortcuts (Ctrl+/Ctrl-), synced across tabs

Fixed

  • Sidebar tab not remembering last selection
  • Removed dead Tauri bridge iframe code

v0.22.5

Fixed

  • All TypeScript errors resolved across the frontend codebase
  • Removed dead shell/console feature code

v0.21.0

Added

  • Gallery channels with image thumbnails and lightbox viewer
  • Automated backup script with remote pg_dump and file sync
  • GitHub Action to auto-update AUR package on release
  • .desktop file categories for Linux packaging

Fixed

  • 5 WebRTC video/screen sharing bugs
  • WebRTC memory leak in voice connections
  • Voice rate limiting and broadcast channel hardening
  • ICE candidates silently dropped (voice calls failing)
  • Voice auto-kick race condition with multiple tabs
  • RwLock crash risk in concurrent access paths
  • Group invite permissions for instance owner
  • 3-person mesh call connectivity
  • Desktop app auto-update (navigate to server SPA on version mismatch)
  • Docker build race condition (separate cargo cache IDs per stage)
  • CSP blocking SvelteKit (per-request nonce injection)

Security

  • Multiple security hardening rounds: CORS, TOTP encryption, EXIF stripping, WebSocket auth
  • JWT audience validation, prekey verification
  • Secrets management overhaul, rate limiting, auth audit logging
  • Fixed 2 critical + 5 high findings from security audit
  • CSP hardened with nonce-based script policy
  • Replaced unwrap() with proper error handling in channel routes

v0.20.0

  • Permissions overhaul: unified 5-tier role hierarchy (Instance Owner > Instance Admin > Owner/Admin > Moderator > Member)
  • Instance Owner can now delete any message anywhere, including other users' DMs
  • Instance Admin now bypasses community membership checks (previously only Instance Owner could)
  • Channel moderator role: owners can promote members to moderator in channel member panels
  • Moderators can delete others' messages, pin/unpin messages, close polls, and kick/ban members
  • Role cycling in channel member panel for intuitive role management
  • Consolidated duplicate permission logic across server codebase

v0.17.0

  • Add ARM64 support with multi-architecture Docker builds (amd64 + arm64)
  • Add pre-built container images on GHCR
  • Add CI workflow for automated multi-arch builds
  • Add platform detection in install script

v0.16.0

  • Add threaded replies with thread panel UI
  • Add read receipts with real-time broadcast
  • Add privacy toggle for read receipts
  • Thread panel: rich message rendering, hover actions, reactions, composer

v0.15.0

  • Add message edit history tracking
  • Add edit history viewer UI
  • Track old ciphertext/nonce on each edit

v0.14.x

  • Add search filters: sender, date range, file type
  • Add scheduled messages with send-later UI and scheduled panel
  • Add bookmarks ("Saved Items") panel
  • Add bio and pronouns to user profiles and profile cards
  • Accessibility improvements: ARIA labels throughout
  • v0.14.1: Error feedback improvements, minor fixes

v0.13.x

  • Add announcements system (admin → all users)
  • Add custom emoji (50 per community, PNG/GIF/WebP)
  • Add content reporting system
  • Add idle status tracking
  • v0.13.1: Mark-all-read, confirmation dialogs, type fixes
  • v0.13.2: Accessibility and code quality improvements
  • v0.13.3: Responsive UI and server hardening

v0.12.0

  • Add polls (2-10 options, multi-select, anonymous, expiry)
  • Add webhooks (per-channel, configurable name/avatar)

v0.11.0

  • Add theme customization: 8 color palettes, 8 accent colors
  • Add message density options (cozy/compact)
  • Add font size settings (small/medium/large)
  • Add custom theme support

v0.10.0

  • Add permissions system for groups, channels, and communities
  • Add group and channel settings popovers
  • Add community policies (who can create groups, who can create invites)

v0.9.0

  • Add mobile-responsive layout
  • Add touch-friendly UI components
  • Add adaptive media sizing for small screens

v0.8.0

  • Add preset themes: Default, Monokai, Dracula, Nord, Solarized, AMOLED, Catppuccin
  • Add custom theme editor
  • Add bubble-style message layout option
  • Add relative timestamps
  • Add reduce-motion accessibility option

v0.7.0

  • Add invite link system with codes, expiry, and usage limits
  • Add WebSocket connection cleanup and reconnection
  • Add accessibility improvements
  • Security hardening: rate limiting, input validation

v0.5.x

  • Add voice channel volume amplification (0-500%)
  • Add screen sharing with audio pipeline
  • Fix screen share context menu and audio capture
  • Add PipeWire system audio auto-capture

v0.1.0

Initial release.

  • Communities, groups, and channels
  • Text messaging with Markdown formatting
  • Voice and video calls (WebRTC full-mesh)
  • Direct messages
  • End-to-end encryption infrastructure (X3DH + Double Ratchet + Sender Keys)
  • File uploads with previews
  • Emoji reactions
  • User profiles with avatars, status, and presence
  • Admin panel (users, invites, files, reports, audit log)
  • Moderation tools (warn, timeout, kick, ban)
  • Dark/light theme with system preference detection
  • Docker deployment with PostgreSQL
  • PWA support

Versioning Policy

Chatalot uses semantic versioning: - Major (x.0.0): Breaking changes to the API or database schema - Minor (0.x.0): New features, non-breaking changes - Patch (0.0.x): Bug fixes, minor improvements

Database migrations are applied automatically on server startup. Breaking schema changes will include migration scripts.

  • Feature Status -- Current implementation status of all features
  • FAQ -- Frequently asked questions