ARFC-1007: Contextual Chat System
Status
| Implemented | Draft Date: 2026-06-17 | Last Call Date: 2026-06-30 | Publication Date: 2026-06-30 | Version: 0.5 |
Errata (v0.2–0.5, 2026-06-18): Corrections arising from the #2091 technical design. See Revision History. In summary: (1) Challenge context is deferred to a later version; (2)
/chat/messages/postisrealm-scoped with thep ∈ members(thread(eid))predicate, notself; (3) V1 thread membership is manager-add only — no self-join (§3.4); (4) thread discovery / listing and thread-metadata visibility are governed by a new realm-scoped/chat/threads/viewaction, rather than a computed “manager” tier (§3.12); (5) channels are materialized lazily via a newPOST /v1/chat/channelsresolve-or-create endpoint and/chat/channels/createaction (§3.11, §3.12).
Abstract
This ARFC proposes a contextual chat system for the Codermerlin Academy API. The system supports moderated, auditable conversations associated with explicit platform contexts such as teams, invitations, join requests, realms, lessons, and challenges. Version 1 includes channels, threads, messages, message history, membership history, unread counts, REST APIs, and server-sent event notifications, while intentionally excluding direct user-to-user messaging, attachments, images, hyperlinks, typing indicators, presence indicators, and search.
Table of Contents
- Introduction
- Motivation
- Specification
- Version 1 Scope
- Security Considerations
- Backward Compatibility
- References
- Author
- Revision History
1. Introduction
The platform needs communication tools for workflows where users must coordinate around a specific educational or operational object. The initial use case is team formation, where communication is necessary between team managers, members, invited users, and applicants who may not yet belong to the same team.
The chat system is built around explicit contexts. Users cannot arbitrarily message other users. Every conversation belongs to a channel that is owned by a platform object, and every thread belongs to a channel.
2. Motivation
Current State
The platform supports teams, team membership requests, realm membership, lessons, challenges, and moderated educational workflows. Some of these workflows require communication among users who do not yet share a direct durable relationship.
Missing Capabilities
- Users need to communicate during team formation without enabling unrestricted direct messaging.
- Instructors and authorized moderators need oversight over conversations within their realm.
- Message edits and deletions need to preserve history for accountability.
- Clients need unread counts and event notifications for responsive chat experiences.
- Conversations need to remain tied to discoverable platform contexts.
Design Goals
- Keep communication contextual rather than user-to-user by default.
- Support explicit thread membership and membership history.
- Preserve message history across edits and deletions.
- Allow invisible moderator observation where authorized.
- Support REST APIs and server-sent event notifications in Version 1.
- Exclude features that add moderation or storage complexity from Version 1, including attachments, images, hyperlinks, typing indicators, presence indicators, and search.
3. Specification
3.1 Core Principles
Communication Is Contextual
Every conversation exists within a specific context such as a team, team invitation, team join request, realm, lesson, or challenge.
Users may participate only when they are eligible for the owning context and are explicit members of the thread.
Communication Is Moderated
Authorized instructors and moderators may observe conversations within their realm according to their privileges.
Moderators may invisibly observe, or lurk, without appearing in member lists or presence indicators.
Communication Is Auditable
Messages may be edited and deleted by their authors when the author has the appropriate privilege. All revisions remain available.
Deleted messages remain in the timeline as deletion indicators, with historical revisions available through message history APIs.
3.2 Primary Objects
Channel
A channel represents communication associated with one owning object.
Characteristics:
- owned by exactly one platform object
- visible to users eligible for the owning object
- contains one or more threads
- defines the broad eligibility boundary for conversation
Thread
A thread is a conversation within a channel.
Characteristics:
- belongs to exactly one channel
- has an explicit title
- has explicit membership
- cannot be moved to another channel
- is either ongoing or terminated
Creating a thread does not automatically add any members. The creator is not special unless separately added as a member.
Message
A message belongs to a thread and is authored by either a participating user or an authorized moderator using moderator posting privileges.
Messages support a constrained text format defined in Content Rules.
Status Message
A status message is a system-generated timeline event.
Examples:
- member joined
- member left
- member removed
- member re-added
- thread created
- thread terminated
- message deleted
Status messages are not counted as unread user messages.
3.3 Context Types
Version 1 supports chat channels associated with these context families:
| Context | Purpose |
|---|---|
| Team | Communication among team members and eligible team managers |
| Team invitation | Communication between inviter, invitee, and eligible team managers |
| Team join request | Communication between applicant and eligible team managers |
| Realm | Realm-scoped announcements or coordination threads |
| Lesson | Discussion connected to lesson participation or instruction |
Errata (v0.2): The Challenge context is deferred to a later version.
Challengeslink only tomissionIDwith no direct challenge→realm column, so the owning realm is not cleanly resolvable for eligibility under the predicate model. Version 1 ships the Team, Team invitation, Team join request, Realm, and Lesson contexts (which cover the primary team-formation use case); Challenge channels follow once a challenge→realm resolution path is defined.
Additional context types may be added in later versions if they preserve the same eligibility and moderation model.
3.4 Thread Membership
Thread membership is always explicit.
A user may participate in a thread only when both conditions are true:
- The user is eligible for the channel’s owning context.
- The user is currently a thread member.
Membership events are preserved as history. A member may be added, leave, be removed, and be re-added.
Errata (v0.3): In Version 1, thread membership is manager-driven only: an authorized manager adds members, and a member may leave; managers may remove and re-add. There is no self-join action or endpoint in V1. Earlier wording (“a user may join”) is read as “be added by an authorized manager.” Self-join may be introduced in a later version (it would also require a joinable-thread discovery mechanism, since the thread list is otherwise restricted to members and authorized lurkers).
Membership Gaps
If a user leaves a thread and later rejoins, messages from periods when the user was not a member are hidden from that user.
Clients should show indicators that hidden messages exist during those membership gaps without revealing the hidden message content.
Conceptual visibility rule:
visibleToUser(message, user) :=
user is eligible for channel context
AND (
user has moderator lurk privilege
OR message timestamp falls inside one of user's thread membership intervals
)
3.5 Thread Lifecycle
Threads have two states:
| State | Description |
|---|---|
| Ongoing | Members may post, edit, delete, and manage membership according to privileges |
| Terminated | Thread remains visible but no new user messages or membership changes are accepted |
Terminated threads cannot be reopened. A new thread must be created instead.
3.6 Message Lifecycle
Posting
Current thread members may post user messages when they have
/chat/messages/post{realm | p ∈ members(thread(eid))} for the thread context.
Errata (v0.2): Posting was originally written as
/chat/messages/post{self}. Posting authority derives from current thread membership in an eligible channel, not fromselfownership, so the action isrealm-scoped with thep ∈ members(thread(eid))predicate, re-evaluated per request.selfscope is reserved for editing/deleting one’s own message.
Moderators may post without becoming thread members when they have /chat/messages/postAsModerator{realm}.
Editing
Authors may edit their own messages when they have the appropriate message edit privilege.
All revisions remain available through message history APIs.
Deleting
Authors may delete their own messages when they have the appropriate message delete privilege.
Deleted messages:
- remain in the timeline
- display as “Message deleted”
- do not count as unread messages
- retain revision history
3.7 Content Rules
Version 1 user messages support:
- Unicode text
- italic text
- bold text
- inline code
- fenced code blocks
Version 1 user messages do not support:
- attachments
- images
- embedded content
- clickable hyperlinks
Clients should render unsupported markup as plain text or reject it before submission, depending on endpoint validation behavior.
3.8 Unread Counts
Unread counts are based on visible user messages only.
Unread counts exclude:
- status messages
- deleted messages
- hidden messages outside the user’s membership intervals
Unread count changes are included in server-sent event notifications.
3.9 Moderator Behavior
Lurking
Users with /chat/threads/lurk{realm} may view any thread, message history, and membership history within the authorized realm.
Lurking does not:
- add the moderator as a thread member
- show the moderator in member lists
- create participant-facing presence indicators
Moderator Posting
Users with /chat/messages/postAsModerator{realm} may post into a thread without becoming thread members.
Moderator posts are visible as authored moderator messages and are subject to the same history and audit behavior as other messages.
3.10 Real-Time Notifications
Version 1 uses server-sent events for client notifications.
SSE notifications cover:
- new messages
- message edits
- message deletions
- membership changes
- thread changes
- unread count changes
SSE notifications are hints that clients should refresh affected resources through REST APIs. REST remains authoritative.
3.11 API Endpoints
The exact route shape may be adjusted during implementation to match existing API naming conventions, but Version 1 exposes these client capabilities:
| Method | Path | operationId | Description |
|---|---|---|---|
| GET | /v1/chat/channels |
listChatChannelsV1 |
List channels visible to the current user |
| POST | /v1/chat/channels |
resolveChatChannelV1 |
Resolve (creating on first use) the channel for an owning object (errata v0.5) |
| GET | /v1/chat/channels/{channel-eid} |
readChatChannelV1 |
Read channel metadata |
| GET | /v1/chat/channels/{channel-eid}/threads |
listChatThreadsV1 |
List visible threads in a channel |
| POST | /v1/chat/channels/{channel-eid}/threads |
createChatThreadV1 |
Create a thread in a channel |
| GET | /v1/chat/threads/{thread-eid} |
readChatThreadV1 |
Read thread metadata |
| PATCH | /v1/chat/threads/{thread-eid} |
updateChatThreadV1 |
Update thread title or metadata |
| POST | /v1/chat/threads/{thread-eid}/terminate |
terminateChatThreadV1 |
Terminate a thread |
| GET | /v1/chat/threads/{thread-eid}/members |
listChatThreadMembersV1 |
List current thread members |
| GET | /v1/chat/threads/{thread-eid}/membership-history |
listChatThreadMembershipHistoryV1 |
List membership history visible to the caller |
| POST | /v1/chat/threads/{thread-eid}/members/{user-eid} |
addChatThreadMemberV1 |
Add a user to a thread |
| DELETE | /v1/chat/threads/{thread-eid}/members/{user-eid} |
removeChatThreadMemberV1 |
Remove a user from a thread |
| POST | /v1/chat/threads/{thread-eid}/leave |
leaveChatThreadV1 |
Leave a thread as the current user |
| GET | /v1/chat/threads/{thread-eid}/messages |
listChatMessagesV1 |
List visible thread messages |
| POST | /v1/chat/threads/{thread-eid}/messages |
postChatMessageV1 |
Post a user message |
| POST | /v1/chat/threads/{thread-eid}/moderator-messages |
postChatModeratorMessageV1 |
Post as a moderator without joining |
| GET | /v1/chat/messages/{message-eid} |
readChatMessageV1 |
Read a message |
| PATCH | /v1/chat/messages/{message-eid} |
editChatMessageV1 |
Edit an authored message |
| DELETE | /v1/chat/messages/{message-eid} |
deleteChatMessageV1 |
Delete an authored message |
| GET | /v1/chat/messages/{message-eid}/history |
listChatMessageHistoryV1 |
List message revisions visible to the caller |
| GET | /v1/chat/unread-counts |
listChatUnreadCountsV1 |
List unread counts for visible channels and threads |
| POST | /v1/chat/threads/{thread-eid}/read-marker |
updateChatReadMarkerV1 |
Mark visible messages read through a client-supplied point |
| GET | /v1/chat/events |
streamChatEventsV1 |
Subscribe to SSE chat notifications |
3.12 Authorization Model
Privileges are expressed as action, scope, and optional predicate.
Examples:
| Privilege | Scope | Predicate | Purpose |
|---|---|---|---|
/chat/threads/create |
realm |
none | Create threads in eligible realm-scoped contexts |
/chat/threads/create |
realm |
p in team.member |
Create threads for teams where the principal is a team member |
/chat/threads/create |
realm |
p in team.manager |
Create threads for teams managed by the principal |
/chat/threads/addMember |
realm |
p in team.manager |
Add eligible users to team-related threads |
/chat/threads/removeMember |
self |
none | Remove self from a thread |
/chat/threads/removeMember |
realm |
p in team.manager |
Remove users from team-related threads |
/chat/threads/terminate |
realm |
p in team.manager |
Terminate team-related threads |
/chat/channels/create |
realm |
eligibility | Resolve-or-create the channel for an owning object the principal is eligible for (errata v0.5) |
/chat/threads/view |
realm |
none | Discover/list threads and read thread metadata realm-wide without membership (errata v0.4) |
/chat/threads/lurk |
realm |
none | Invisibly observe threads in the authorized realm |
/chat/messages/post |
realm |
p ∈ members(thread(eid)) |
Post as a current thread member (errata v0.2: was self/none) |
/chat/messages/edit |
self |
none | Edit own messages |
/chat/messages/delete |
self |
none | Delete own messages |
/chat/messages/postAsModerator |
realm |
none | Post as a moderator without joining |
/chat/messages/history/read |
self |
none | Read history for own visible messages |
/chat/messages/history/read |
realm |
none | Read message history as a moderator |
Predicate examples:
p in team.memberp in team.managerp in team.candidates.inviteep in team.candidates.applicantp in team.member AND p not in team.manager
3.13 Error Handling
Authorization failures use the platform’s standard structured error model.
Expected error categories include:
| Condition | Expected behavior |
|---|---|
| Caller is not eligible for the channel context | Authorization failure |
| Caller is not a current thread member | Authorization failure unless caller is an authorized lurker or moderator poster |
| Thread is terminated | Validation failure for mutating operations |
| Message content includes unsupported features | Validation failure |
| Requested message falls in membership gap | Not visible to caller |
| Requested object does not exist or is not visible | Standard not-found or authorization-concealed response according to platform conventions |
4. Version 1 Scope
Included
- channels
- threads
- messages
- message editing
- message deletion
- message history
- membership history
- status messages
- unread counts
- REST APIs
- SSE notifications
- moderator lurking
- moderator posting
Excluded
- attachments
- images
- hyperlinks
- embedded content
- typing indicators
- presence indicators
- search
- direct user-to-user messaging
5. Security Considerations
Context Boundary Enforcement
Every read and write must enforce the channel context eligibility rule. Thread membership alone is insufficient to grant access.
Minor Safety and Moderation
The system is designed for an educational environment that may include minors. Moderator lurking, message history, deletion indicators, and contextual communication boundaries are required safety features rather than optional enhancements.
Auditability
Message revisions, deletions, thread membership changes, and moderator posting must remain auditable through client-visible history APIs where authorized.
Moderator Privacy
Lurking must not expose moderator identity through member lists, presence indicators, unread counts, or participant-facing events.
Content Safety
Version 1 excludes hyperlinks, attachments, images, embedded content, and search to reduce abuse and moderation risk.
Rate Limiting
Chat endpoints and SSE subscriptions should use normal platform rate limiting. Message posting, message editing, message deletion, and membership mutation endpoints should be protected against burst abuse.
6. Backward Compatibility
This ARFC introduces new APIs and does not change existing endpoint behavior.
Existing team, realm, lesson, and challenge workflows may begin linking to chat channels after implementation. Clients that do not use chat APIs are unaffected.
No migration is required for existing users or resources in Version 1.
7. References
Related ARFCs
- ARFC-1004: Teams, Leagues, Seasons, and Competitive Gaming Schema
- ARFC-1006: Predefined Link Actions
Related Platform Documentation
8. Author
ĀYŌDÈ Development Team
Codermerlin Academy Architecture
9. Revision History
| Version | Date | Summary |
|---|---|---|
| 0.1 | 2026-06-17 | Initial draft. |
| 0.2 | 2026-06-18 | Errata from the #2091 technical design: (1) Challenge context deferred to a later version — Challenges link only to missionID, so the owning realm is not cleanly resolvable for predicate-based eligibility; V1 ships Team, Team invitation, Team join request, Realm, and Lesson contexts (§3.3). (2) /chat/messages/post corrected from self/none to realm-scoped with the p ∈ members(thread(eid)) predicate — posting authority derives from current thread membership, not self ownership; self remains for edit/delete of one’s own message (§3.6, §3.12). |
| 0.3 | 2026-06-18 | Erratum from the #2091 technical design: V1 thread membership is manager-add only — managers add members; members may leave; managers may remove/re-add. No self-join action or endpoint in V1; “a user may join” is read as “be added by an authorized manager” (§3.4). |
| 0.4 | 2026-06-18 | Erratum from the #2091 technical design: thread discovery / listing and thread-metadata visibility are governed by a new realm-scoped /chat/threads/view action (top predicate), rather than a computed “manager” tier. A caller sees threads they are a current member of, plus — if granted /chat/threads/view{realm} (or /chat/threads/lurk{realm}) — all threads in the realm; plain channel eligibility exposes no thread titles or existence. The per-owner management predicate continues to gate management actions (create/update/terminate/addMember/removeMember); managers are additionally granted /chat/threads/view{realm} for discovery. Scope is realm-wide, matching lurk (§3.12). |
| 0.5 | 2026-06-18 | Erratum from the #2091 technical design: channels are materialized lazily via a new POST /v1/chat/channels resolve-or-create endpoint (resolveChatChannelV1) and a /chat/channels/create action (realm scope, eligibility predicate). The caller supplies the owning object (ownerType + ownerEID) and receives the channel EID, which the thread endpoints then address; the operation is idempotent (one channel per owning object) and authorized by channel eligibility. listChatChannelsV1 lists existing channels only — in V1, the caller’s member channels; realm-wide channel enumeration for /chat/threads/view / /chat/threads/lurk holders is deferred pending a clean channel-to-realm scoping (§3.11, §3.12). |