Browse Source

Document the int-enum wire shape as a realtime pitfall

Liv Klumpp 1 tuần trước cách đây
mục cha
commit
7b466d32e9
2 tập tin đã thay đổi với 5 bổ sung và 2 xóa
  1. 4 2
      adapter.py
  2. 1 0
      skills/chatto-platform-plugin/SKILL.md

+ 4 - 2
adapter.py

@@ -70,6 +70,9 @@ from gateway.session import SessionSource
 # imports would load a second, distinct copy of every module, so isinstance()
 # checks across the two copies would silently fail.
 try:
+    from chattolib._pb.chatto.api.v1.presence_pb2 import (
+        PresenceStatus as ProtoPresenceStatus,  # type: ignore[import-untyped]
+    )
     from chattolib._pb.chatto.realtime.v1 import (  # noqa: F401 — importability probe
         realtime_pb2,
     )
@@ -77,7 +80,6 @@ try:
         MessageMention,  # type: ignore[import-untyped]
         MessagePostedEvent,  # type: ignore[import-untyped]
     )
-    from chattolib._pb.chatto.api.v1 import presence_pb2  # type: ignore[import-untyped]
     from chattolib.client import (
         ChattoClient,
     )
@@ -2675,7 +2677,7 @@ class ChattoAdapter(BasePlatformAdapter):
                 if isinstance(raw_status, PresenceStatus):
                     status = raw_status
                 else:
-                    proto_name = presence_pb2.PresenceStatus.Name(int(raw_status))
+                    proto_name = ProtoPresenceStatus.Name(int(raw_status))
                     status = next(
                         (
                             member

+ 1 - 0
skills/chatto-platform-plugin/SKILL.md

@@ -104,6 +104,7 @@ Procedure when the bot answered a channel message it should not have:
 
 Pitfalls:
 
+- **Realtime payload fields may arrive as plain ints, not proto enum wrappers.** `PresenceChangedEvent.status` is an `int` on the wire (protobuf runtime without native enums), so `payload.status.name` raised `AttributeError: 'int' object has no attribute 'name'` on EVERY presence change — and each one cost a 30s reconnect gap that silently swallowed inbound messages. Coerce through `presence_pb2.PresenceStatus.Name(int(...))` and map to chattolib's `PresenceStatus` by value (the coercion chattolib's own `bot.py` applies to the same field). Tests must mock the WIRE shape — the existing tests mocked `status` as a StrEnum (which has `.name`) and so never caught it; a regression test with `payload.status = 1` pins the real contract.
 - **Fail-open gates degrade silently.** An unresolved handle reads as "not a mention", so the bot barges in on every @colleague message with no log line louder than DEBUG. The fix is a fuller local cache, never a per-handle lookup: an admin-directory call (`admin_get_member`) needs permissions a bot account must not hold, and its `permission_denied` failed *every* lookup in the field, which fail-open turned into "answer every @colleague message". Prefer what the roster projection already holds, and keep it fed — the OPEN admission path fetches it precisely so this check has something to read.
 - **Mentions are extracted from body text** (`ChattoConstants.MENTION_RE`) outside code fences and inline code spans, matching the upstream web frontend — a handle quoted in code does not gate, and case-insensitivity applies throughout.