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/post is realm-scoped with the p ∈ members(thread(eid)) predicate, not self; (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/view action, rather than a computed “manager” tier (§3.12); (5) channels are materialized lazily via a new POST /v1/chat/channels resolve-or-create endpoint and /chat/channels/create action (§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

  1. Introduction
  2. Motivation
  3. Specification
  4. Version 1 Scope
  5. Security Considerations
  6. Backward Compatibility
  7. References
  8. Author
  9. 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

  1. Users need to communicate during team formation without enabling unrestricted direct messaging.
  2. Instructors and authorized moderators need oversight over conversations within their realm.
  3. Message edits and deletions need to preserve history for accountability.
  4. Clients need unread counts and event notifications for responsive chat experiences.
  5. Conversations need to remain tied to discoverable platform contexts.

Design Goals

  1. Keep communication contextual rather than user-to-user by default.
  2. Support explicit thread membership and membership history.
  3. Preserve message history across edits and deletions.
  4. Allow invisible moderator observation where authorized.
  5. Support REST APIs and server-sent event notifications in Version 1.
  6. 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:

Thread

A thread is a conversation within a channel.

Characteristics:

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:

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. Challenges link only to missionID with 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:

  1. The user is eligible for the channel’s owning context.
  2. 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 from self ownership, so the action is realm-scoped with the p ∈ members(thread(eid)) predicate, re-evaluated per request. self scope 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:

3.7 Content Rules

Version 1 user messages support:

Version 1 user messages do not support:

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:

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:

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:

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:

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

Excluded


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


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).