Bläddra i källkod

Track Chatto v0.5.0-beta.14

Regenerated proto bindings and vendored ConnectRPC stubs from
chattocorp/chatto@v0.5.0-beta.14. The wire delta is small and additive:
admin GetSystemInfoResponse gained field 6 'livekit' (AdminLiveKitStatus:
enabled/configured flags, server url + api_key identifier, webhook config,
AdminLiveKitConnectionState reachability result, insecure-url warning, and
last webhook receipt times; never the API/webhook secrets), and
CreatePinnedMessage/DeletePinnedMessage are now gated by a dedicated
'message.pin' permission instead of 'room.manage' (comment-only proto
change — server-side enforcement, no message-shape change). The raw dict
from admin_get_system_info() picks up the livekit block automatically via
pb_to_dict, so no client code change was needed; added a regression test
covering the livekit rendering and documented the pin-permission gate as a
Gotcha in AGENTS.md.

Verification: ruff format/check clean (27 files); mypy adds zero new errors
vs baseline (6 pre-existing import-untyped); pytest 93 passed, 2 skipped
(incl. new test_admin_get_system_info_livekit); live GetServer against
https://chat.chatto.run returns version 0.5.0-beta.14; live bot integration
receive-path tests pass (2 passed, second-user test skipped).

Bumped version 0.5.0b13 -> 0.5.0b14.
the-codemancer 2 veckor sedan
förälder
incheckning
40fe1548c5

+ 2 - 1
AGENTS.md

@@ -110,7 +110,7 @@ to its grants.
 | Admin: users | `chatto.admin.v1.AdminUserService` | `ListMembers`, `GetMember`, `BatchGetMembers`, `AssignRole`, `RevokeRole`, `UpdateUserPassword`, `ClearUsernameCooldown`, `DeleteUser` (the old `UpdateUser` admin RPC was removed in 0.5.0b9 — privileged profile edits go through `UserService.UpdateUserProfile`) |
 | Admin: roles | `chatto.admin.v1.AdminRoleService` | `ListRoles`, `GetRole`, `CreateRole`, `UpdateRole`, `DeleteRole`, `ReorderRoles` |
 | Admin: event log | `chatto.admin.v1.AdminEventLogService` | `ListEvents`, `ListEventTypes`, `GetEvent` (raw response) |
-| Admin: diagnostics | `chatto.admin.v1.AdminDiagnosticsService` | `GetSystemInfo` (raw response) |
+| Admin: diagnostics | `chatto.admin.v1.AdminDiagnosticsService` | `GetSystemInfo` (raw response; since 0.5.0b14 also carries a `livekit` `AdminLiveKitStatus` block — setup/config state, server-API reachability `connection_state`, webhook times; never secrets) |
 | Admin: permissions | `chatto.admin.v1.AdminPermissionService` | `GetRole/UserPermissionMatrix`, `ListRole/UserPermissionDecisions`, `ExplainPermissions`, `SetRolePermission`, `SetUserPermission` (raw responses where the permission shape is server-version-dependent) |
 | Voice calls | `VoiceCallService` | `ListActiveCalls`, `GetActiveCall`, `BatchGetActiveCalls`, `JoinCall`, `LeaveCall`, `CreateCallToken` |
 | Realtime (WS) | `chatto.realtime.v1` protobuf WS (protocol v4) | `stream_events(client)` / `RealtimeConnection` — the client sends one `RealtimeSubscribe` handshake; the server streams `event` (a 48-member oneof of thin, caller-scoped hints, incl. `viewer_presence_preference_changed` added in 0.5.0b6), `snapshot`, `caught_up`, `heartbeat`, and `close` (with a `RealtimeCloseCode` and `retry_after`) |
@@ -140,6 +140,7 @@ to its grants.
 - **0.5.0b8 added the public `ServerDiscoveryService.ListNeighborhoodServers` RPC** (no session needed; the server caches background discovery, refreshed at least hourly). `NeighborhoodServer.profile` is a `ServerPublicProfile` **without** the welcome message, and its logo/banner URLs are server-relative paths of copies on the *called* server — resolve them against that server's origin, not the neighbor's. `ListFollowedThreadsRequest.unread_only` filters to followed threads with unread replies (older servers ignore the field and return everything; the page total then counts only the filtered set).
 - **0.5.0b9 consolidated all public-profile edits into a new `chatto.api.v1.UserService.UpdateUserProfile` RPC (breaking for both edit paths).** `MyAccountService.UpdateProfile` was removed and `chatto.admin.v1.AdminUserService.UpdateUser` was removed; the new RPC takes a required `user_id` plus `display_name`/`login`/`bio` (an inferred `update_mask`) and returns just the updated `User` — no admin member row. Self-updates need no extra authority; editing another human requires `user.manage-accounts` (and, for a bot target, ownership of the bot, `user.manage-accounts`, or `bot.manage`), and a `user.manage-accounts` caller bypasses the username-change cooldown without starting one. Client: new `update_user_profile(user_id, ...)` plus a self-service `update_profile(...)` that resolves the caller's id via the viewer snapshot and delegates (same public signature as before, plus `bio`); `admin_update_user(...)` keeps its name/shape but now delegates to the same RPC and its second return value is always `None` (the removed response no longer carries an `AdminMember`) — re-fetch with `admin_get_member()` when the row is needed. `User.bio` is now documented as Markdown. The vendored `user_service.proto` also newly carries `ListUsers`/`GetUser`/`BatchGetUsers` (directory) and `UploadAvatar`/`DeleteAvatar`, for which chattolib still ships no wrappers.
 - **0.5.0b10: DM rooms keep deleted participants in `member_user_ids`.** Since 0.5.0-beta.10, `RoomWithViewerState.member_user_ids` on a DM includes participants whose accounts were deleted (the directory no longer prunes them); clients render those as deleted users. Channel rooms still carry an empty list. The wire shape is unchanged (same field, same number) and `RoomWithViewerState.parse` already copies the full list — so no code change was needed, but code that resolves every `member_user_ids` entry to a `User` must tolerate users that `get_user` no longer returns.
+- **0.5.0b14: pinning is gated by a dedicated `message.pin` permission (server-side behavior change; no wire change).** `RoomService.CreatePinnedMessage`/`DeletePinnedMessage` required `room.manage` until 0.5.0-beta.13; since 0.5.0-beta.14 they require the new `message.pin` permission instead. The RPCs, their request/response messages, and chattolib's `pin_message`/`unpin_message`/`list_pinned_messages` wrappers are unchanged — but a bot/role that pinned via `room.manage` must now hold `message.pin` (or the server returns `PERMISSION_DENIED`). Same release added an additive `livekit` (`AdminLiveKitStatus`) field 6 to admin `GetSystemInfoResponse` (LiveKit setup/config state, server-API reachability as `AdminLiveKitConnectionState`, webhook receipt times; never the API/webhook secrets) — `admin_get_system_info()` returns it raw in `data["livekit"]` with no client change.
 - **Never regenerate protos from `chattocorp/chatto@main`.** `scripts/generate_pb.sh` defaults to a tag derived from `pyproject.toml`: `v<base>` for finals, and `v<base>-<kind>.<n>` for PEP 440 pre-releases (`0.5.0b6` → `v0.5.0-beta.6`), probing candidates with `git ls-remote` (exact ref match; the REST `/tags/{name}` endpoint 404s on lightweight tags). Override with `CHATTO_REF=<tag>` only when you know the deployed server has caught up. chattolib 0.4.19 shipped once from `main` and broke realtime for every downstream client because Chatto's main had a protocol-v2 rewrite that the deployed 0.4.19 server did not speak.
 
 ### Known bugs

+ 54 - 2
proto/chatto/admin/v1/diagnostics.proto

@@ -19,6 +19,57 @@ message GetSystemInfoResponse {
   repeated AdminDurableWorkerStatus durable_workers = 4;
   // Whether projection diagnostics were collected successfully.
   optional bool projections_available = 5;
+  // LiveKit voice and video call setup diagnostics.
+  AdminLiveKitStatus livekit = 6;
+}
+
+// LiveKit voice and video call setup as seen by the Chatto server that
+// handled the request. The response never includes the API secret or the
+// webhook signing secret.
+//
+// Chatto calls the LiveKit server API once for each request to check
+// reachability and credentials. Webhook times come from the memory of the
+// Chatto server that handled the request. They reset when that server
+// restarts, and other servers in a cluster keep their own times.
+message AdminLiveKitStatus {
+  // Whether `livekit.enabled` is set.
+  bool enabled = 1;
+  // Whether LiveKit is enabled and the URL, API key, and API secret are all set.
+  bool configured = 2;
+  // Configured LiveKit server URL that clients connect to.
+  string url = 3;
+  // Configured LiveKit API key. This is an identifier, not a secret.
+  string api_key = 4;
+  // Whether webhooks use a signing key that differs from `api_key`.
+  bool separate_webhook_key = 5;
+  // URL that LiveKit must send webhooks to. Empty when the public server URL is not set.
+  string webhook_url = 6;
+  // Result of the LiveKit server API check.
+  AdminLiveKitConnectionState connection_state = 7;
+  // Error detail when the check fails. Never contains secrets.
+  string connection_error = 8;
+  // True when `url` uses unencrypted `ws://` or `http://` and the public server URL host is not a loopback address or a `.localhost` name.
+  bool insecure_url = 9;
+  // Time of the last webhook with a valid signature that this server received.
+  google.protobuf.Timestamp last_webhook_at = 10;
+  // Time of the last webhook that this server rejected because of an invalid signature.
+  google.protobuf.Timestamp last_rejected_webhook_at = 11;
+}
+
+// Result of the LiveKit server API check.
+enum AdminLiveKitConnectionState {
+  // Unknown state.
+  ADMIN_LIVE_KIT_CONNECTION_STATE_UNSPECIFIED = 0;
+  // LiveKit is not configured, so Chatto did not do the check.
+  ADMIN_LIVE_KIT_CONNECTION_STATE_NOT_CONFIGURED = 1;
+  // LiveKit accepted the API call with the configured key and secret.
+  ADMIN_LIVE_KIT_CONNECTION_STATE_OK = 2;
+  // Chatto could not connect to the LiveKit server URL.
+  ADMIN_LIVE_KIT_CONNECTION_STATE_UNREACHABLE = 3;
+  // LiveKit rejected the configured API key or secret.
+  ADMIN_LIVE_KIT_CONNECTION_STATE_UNAUTHORIZED = 4;
+  // The server at the URL replied with an unexpected error.
+  ADMIN_LIVE_KIT_CONNECTION_STATE_ERROR = 5;
 }
 
 // Broker-derived health for one known durable worker queue.
@@ -279,7 +330,8 @@ message AdminProjectionMetric {
 
 // Provides server diagnostics for the admin system page.
 service AdminDiagnosticsService {
-  // Returns broker, JetStream, and projection diagnostics. Requires the
-  // server owner role.
+  // Returns broker, JetStream, projection, and LiveKit diagnostics. Requires
+  // the server owner role. The call checks the LiveKit server API, so it can
+  // take up to five seconds when LiveKit does not reply.
   rpc GetSystemInfo(GetSystemInfoRequest) returns (GetSystemInfoResponse);
 }

+ 2 - 2
proto/chatto/api/v1/rooms.proto

@@ -484,11 +484,11 @@ service RoomService {
   // pins from threads that the caller cannot read. Direct-message rooms do not
   // support pins.
   rpc ListPinnedMessages(ListPinnedMessagesRequest) returns (ListPinnedMessagesResponse);
-  // Pins a current message. The caller must have room.manage and must be able
+  // Pins a current message. The caller must have message.pin and must be able
   // to read the message. Repeating an existing pin is idempotent. Direct-message
   // rooms are rejected.
   rpc CreatePinnedMessage(CreatePinnedMessageRequest) returns (CreatePinnedMessageResponse);
-  // Removes a current pin. The caller must have room.manage. Removing a
+  // Removes a current pin. The caller must have message.pin. Removing a
   // missing pin is idempotent. Direct-message rooms are rejected.
   rpc DeletePinnedMessage(DeletePinnedMessageRequest) returns (DeletePinnedMessageResponse);
   // Refreshes the current user's live-only typing indicator for a room or

+ 1 - 1
pyproject.toml

@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
 
 [project]
 name = "chattolib"
-version = "0.5.0b13"
+version = "0.5.0b14"
 description = "Async Python client library for the Chatto webchat Connect API"
 requires-python = ">=3.11"
 license = "MPL-2.0 AND Apache-2.0"

Filskillnaden har hållts tillbaka eftersom den är för stor
+ 0 - 0
src/chattolib/_pb/chatto/admin/v1/diagnostics_pb2.py


+ 34 - 0
tests/test_client.py

@@ -14,6 +14,7 @@ from unittest.mock import AsyncMock
 import pytest
 
 from chattolib._pb.chatto.admin.v1 import (
+    diagnostics_pb2,
     room_layout_pb2,
 )
 from chattolib._pb.chatto.admin.v1 import (
@@ -905,6 +906,39 @@ async def test_admin_move_sidebar_item(client):
     assert sent.group_id == "g1"
 
 
+async def test_admin_get_system_info_livekit(client):
+    resp = diagnostics_pb2.GetSystemInfoResponse()
+    resp.projections_available = True
+    resp.livekit.enabled = True
+    resp.livekit.configured = True
+    resp.livekit.url = "wss://livekit.example"
+    resp.livekit.api_key = "AP1234"
+    resp.livekit.separate_webhook_key = True
+    resp.livekit.webhook_url = "https://chat.example/api/admin/livekit/webhook"
+    resp.livekit.connection_state = diagnostics_pb2.ADMIN_LIVE_KIT_CONNECTION_STATE_OK
+    resp.livekit.insecure_url = False
+    _mock_method(client, "admin_diagnostics", "get_system_info", resp)
+
+    async with client:
+        data = await client.admin_get_system_info()
+
+    lk = data["livekit"]
+    assert lk["enabled"] is True
+    assert lk["configured"] is True
+    assert lk["url"] == "wss://livekit.example"
+    assert lk["apiKey"] == "AP1234"
+    assert lk["separateWebhookKey"] is True
+    # enums render as their full name in Connect JSON (use_integers_for_enums=False)
+    assert lk["connectionState"] == "ADMIN_LIVE_KIT_CONNECTION_STATE_OK"
+    assert (
+        diagnostics_pb2.AdminLiveKitConnectionState.Value(
+            "ADMIN_LIVE_KIT_CONNECTION_STATE_UNREACHABLE"
+        )
+        == 3
+    )
+    assert "connectionError" not in lk  # empty string omitted by MessageToDict
+
+
 async def test_admin_update_server_config(client):
     resp = admin_server_pb2.UpdateServerConfigResponse()
     resp.config.server_name = "MyServer"

Vissa filer visades inte eftersom för många filer har ändrats