Skip to content
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
0d07847
Add design for message.read RPC in room-service
claude May 4, 2026
35393e5
Add implementation plan for message.read RPC
claude May 4, 2026
d92122f
Add MessageRead and MessageReadWildcard subject builders
claude May 5, 2026
37638a8
Add MessageReadRequest and SubscriptionReadEvent model types
claude May 5, 2026
330128a
Add 4 store methods for message.read flow
claude May 5, 2026
f40b037
Add message.read RPC handler in room-service
claude May 5, 2026
5d0c4b1
Add UpdateSubscriptionRead to InboxStore
claude May 5, 2026
6ad89c1
Apply subscription_read events in inbox-worker
claude May 5, 2026
c3d4980
Merge remote-tracking branch 'origin/main' into claude/add-message-re…
claude May 6, 2026
18179a1
Parallelize independent reads in handleMessageRead
claude May 6, 2026
f72218d
Add temporary test-script.sh for end-to-end message.read RPC verifica…
claude May 6, 2026
9a11e06
Fix test-script.sh siteID and Mongo helpers
claude May 6, 2026
c7f5790
Fix message-read room-ID-mismatch error sanitization + reliable case 9
claude May 6, 2026
abf1af2
test-script.sh: spin up site-b federation harness for scenario 9
claude May 6, 2026
8e7f991
test-script.sh: use JetStream API directly to create INBOX_site-b
claude May 6, 2026
e950abe
test-script.sh: drop redundant filter_subject from INBOX_site-b source
claude May 6, 2026
a4106f0
test-script.sh: ensure OUTBOX_<siteID> exists before scenario 9
claude May 6, 2026
72588b1
test-script.sh: use stream get --last-by-subj for OUTBOX/INBOX checks
claude May 6, 2026
c4f8ad1
test-script.sh: bypass natscli, use JetStream API for stream-msg lookup
claude May 6, 2026
5b9671c
chore: remove test-script.sh file
Allan-Code-hub May 6, 2026
512b9c9
Add bson tags to SubscriptionReadEvent
claude May 6, 2026
2028b28
inbox-worker: use string literal for subscription_read case arm
claude May 6, 2026
5c69162
Merge remote-tracking branch 'origin/main' into claude/add-message-re…
claude May 7, 2026
0d1221f
chore:remove comment
Allan-Code-hub May 7, 2026
c99d581
gofmt: drop stray whitespace + normalize struct field alignment
claude May 7, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 57 additions & 0 deletions docs/client-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -727,6 +727,63 @@ See [Error envelope](#5-error-envelope-reference). Common errors: `"not a member

---

#### Mark Messages Read

**Subject:** `chat.user.{account}.request.room.{roomID}.{siteID}.message.read`
**Reply subject:** auto-generated `_INBOX.>` (NATS request/reply)

This is a **synchronous RPC** — `room-service` performs all writes inline before replying. The handler validates room membership, recomputes the per-subscription `alert` flag, persists the new `lastSeenAt` and `alert` on the user's `Subscription`, optionally recomputes `Room.MinUserLastSeenAt`, and (for cross-site users) publishes a `subscription_read` event to the user's home-site outbox so the destination `inbox-worker` can update its local subscription cache.

##### Request body

| Field | Type | Required | Notes |
|----------|--------|----------|-------|
| `roomId` | string | no | Server derives from subject. If supplied, must match the subject's `roomID`; mismatches are rejected. |

```json
{ "roomId": "01970a4f8c2d7c9aQ" }
```

##### Success response

| Field | Type | Notes |
|----------|--------|-------|
| `status` | string | Always `"accepted"`. Confirms the read receipt was applied. |

```json
{ "status": "accepted" }
```

##### Error response

See [Error envelope](#5-error-envelope-reference). Common errors:

- `"only room members can list members"` — the user has no subscription in the room (sentinel reused across membership-gated RPCs).
- `"room ID mismatch"` — the body's `roomId` doesn't match the subject.
- `"invalid message-read subject: …"` — the subject is malformed.

```json
{ "error": "only room members can list members" }
```

##### Behaviour notes

- **Alert recomputation:** new `alert = oldSub.alert && len(oldSub.threadUnread) > 0`. Reading the room clears the alert when there are no unread thread mentions; it stays set when thread-level unreads remain.
- **`originalLastSeen` resolution:** the handler uses `subscription.lastSeenAt` if present, otherwise falls back to `subscription.joinedAt` (newly-joined rooms have never been read).
- **Room-floor recompute (`Room.MinUserLastSeenAt`):** skipped when `room.lastMsgAt` is `null` or when `originalLastSeen > room.lastMsgAt` (the user was already up-to-date — the floor cannot have moved). Otherwise the handler computes the new floor as `MIN(lastSeenAt OR joinedAt)` across the room's subscriptions and writes it to `rooms.minUserLastSeenAt` (or unsets the field if the aggregate is empty).
- **Cross-site federation:** if the user's home site (`users.siteId`) differs from the handler's site, a `subscription_read` event is published to `outbox.{handlerSite}.to.{userSite}.subscription_read` with payload `{account, roomId, lastSeenAt, alert, timestamp}` (timestamps as `int64` UnixMilli). The destination `inbox-worker` applies the write with an `$lt` order-safety guard so out-of-order delivery cannot regress `lastSeenAt`. The outbox publish happens **before** the room-floor recompute so the user's home site receives every read receipt — even ones that don't move the room floor.
- **No system message, no fan-out events:** read receipts are silent; only the requester receives the `accepted` reply.

##### Triggered events — success path

`None — reply only.` (Cross-site users may observe a delayed `subscription.update` on their home site driven by the outbox/inbox flow above; this is treated as cache convergence rather than a client-visible event for this RPC.)

##### Triggered events — error path

`None — error returned only via the reply subject.`

---

#### List Org Members

**Subject:** `chat.user.{account}.request.orgs.{orgID}.members`
Expand Down
Loading
Loading