Engineering/reviewer-facing knowledge base for how each messaging platform behaves and how OpenAB maps it. Distinct from the operator setup guides in docs/<platform>.md.
This directory is not a giant table — it defines a schema that every platform fills in its own schema/<platform>.toml. The files are the machine-checked source of truth, validated by the crates/platform-schema conformance tests (run in CI). See _template.toml for the blank schema + per-field docs.
Each schema/<platform>.toml has three schema-driven parts:
- Platform capability (
[capability.*], fixed fields) — the platform's intrinsic nature and what a bot can/can't do inside it. Same fields for every platform. Source of truth = official docs (each field carries asourceURL). - OpenAB feature support (
[[openab_features]], the closed 17-feature set) — for each OpenAB capability, astatus+ note + codesource. Source of truth = our code + the PR that decided it. - Platform quirks (
[[quirks]], freeform dated log) — anything that doesn't fit a fixed field (e.g. LINE's reply/push model), plus a findings log.
Sourcing rule: attach the source that answers "why should I trust or keep this?" — intrinsic (A) facts link the official platform doc (a source URL); OpenAB (B) decisions/findings point at the code (file.rs#symbol) and, where relevant, the PR (pr / refs). Code refs use a grep-stable #symbol (no line numbers), so conformance can confirm they still exist without breaking on unrelated edits above the target.
Decision: unlike
[[quirks]], every[[openab_features]]source must currently be a code-ref —feature_sources_exist_in_treerejects URL sources rather than skipping them. This is intentional, not an oversight: no feature currently needs to cite an official-doc URL, and relaxing the check preemptively would let feature sources silently drift to unverifiable doc links instead of code. Revisit if/when a feature genuinely needs a URL source (see #1340).
crates/platform-schema deserializes every schema/*.toml into typed structs and, in CI, enforces:
-
structural validity — required fields, closed enum sets, the exact 17-feature set, unknown-key rejection;
-
version currency — every file's
schema_versionmatches the current one (a stale file fails the build); -
anti-drift — every
sourcecode-ref still resolves to a real file +#symbolin the tree. -
Current schema version:
2026-07-08— the top-lineschema_versionin each file. Bump it when the schema changes; the conformance test then flags every file that hasn't been re-verified.
Known limitation: conformance only checks that a code-ref's file/symbol still exists — it can't detect a symbol whose behavior changed without being renamed or removed, or a note/status that has quietly gone stale while the code-ref it cites remains technically valid. That kind of semantic drift is caught only by PR review (see CONTRIBUTING.md), not by CI. No dedicated per-platform owner is assigned to periodically audit for it; revisit if this proves insufficient in practice.
| Platform | Schema file |
|---|---|
| line | schema/line.toml |
| slack | schema/slack.toml |
| telegram | schema/telegram.toml |
| discord | schema/discord.toml |
| feishu | schema/feishu.toml |
| wecom | schema/wecom.toml |
| googlechat | schema/googlechat.toml |
| teams | schema/teams.toml |
| lineworks | schema/lineworks.toml |
The authoritative field list + types live in _template.toml and the structs in crates/platform-schema/src/lib.rs. Summary below.
Fixed fields, same for every platform; each carries a typed value + note + official-doc source. Use ? in a note only when a fact is genuinely unverified.
| Section | Meaning / allowed values |
|---|---|
transport |
how events arrive: webhook / websocket / socket_mode / long_poll |
inbound_auth |
L1 request-auth / signature scheme: hmac_sha256 / jwt_rs256 / aes / shared_secret / oauth / none |
threads |
native / reply_to_only / emulated / none |
slash_commands |
supported? how registered / delivered? |
mentions |
how the bot detects being addressed: at_mention / username / self_flag / none |
emoji_reactions |
can a bot add / remove reactions? does it receive reaction events? |
edit_message |
can a bot edit its own already-sent message? |
delete_message |
can a bot delete a message? scope: none / own / others / own_and_others |
rich_content |
markdown / cards / buttons support |
attachments |
inbound & outbound media types (image/audio/video/file) + size cap |
message_length_limit |
max chars per outbound message (chunking implication) |
dm_support |
1:1 direct messages supported? |
group_model |
group / channel / room / space taxonomy |
group_sender_identity |
stable per-user sender id in group events: yes / no / consent_gated |
send_model |
any_time / reply_only / push_only / hybrid; reply-token TTL; batch cap |
proactive_push |
can the bot message unsolicited? quota model: unlimited / metered / none |
bot_to_bot |
does the platform deliver other bots' messages to this bot? |
typing_indicator |
supported? |
The closed set of OpenAB capabilities (derived from the ChatAdapter trait in crates/openab-core/src/adapter.rs + the trust/ingress layer). Each block: feature + status + note + source (array of file.rs#symbol) + optional pr.
Status enum: implemented · partial · workaround · not_implemented · n_a (platform can't support it). Always explain workaround / partial — that "why" is the valuable part.
| Feature key | Covers |
|---|---|
send_message |
basic outbound |
message_split |
long-message handling (split_delivery) |
streaming |
stream_begin / stream_append / stream_finish — live vs batched |
reply_quote |
send_message_with_reply |
edit_message |
own-message edit |
delete_message |
delete own / others |
emoji_reactions |
add_reaction / remove_reaction |
threads_topics |
create_thread / create_topic |
media_inbound |
images / files / audio ingestion |
voice_stt |
speech-to-text on voice notes |
trust_gate |
allowlist / identity-trust enforcement point |
deny_echo |
reply-on-deny behavior + delivery constraints |
mention_gating |
require @mention in groups |
slash_commands |
/reset, /cancel handling |
multibot |
multiple bots in one channel |
group_routing |
group / session routing |
cron_dispatch |
scheduled cron job delivery via cronjob.toml |
Freeform dated log — anything not captured by sections 1/2 (special models, gotchas, structural constraints) plus a findings trail. Each block:
date(YYYY-MM-DD),title,note(prose) — required.kind(required):intrinsic(a platform fact) oropenab_decision(a choice/finding of ours).source(optional): official-doc URL, orfile.rs/file.rs#symbol.refs(optional): PR/ADR links, e.g.["#1291"].
┌─────────────────────────────────────────────────────────────────────────┐
│ docs/platforms/schema/*.toml (machine-readable facts, source of truth)│
│ │
│ line.toml │ slack.toml │ discord.toml │ telegram.toml │ feishu.toml │…│
└──────┬──────────────┬──────────────┬────────────────────────────────────┘
│ │ │
▼ ▼ ▼
┌─────────────┐ ┌──────────┐ ┌───────────────────────────────────────────┐
│ CI │ │ Onboard │ │ Future │
│ │ │ │ │ │
│ conformance │ │ new │ │ • runtime capability queries │
│ tests │ │ maintainer│ │ • auto-generated comparison tables │
│ (Rust crate)│ │ reads │ │ • adapter scaffolding from template │
│ │ │ schema │ │ │
└──────┬──────┘ └──────────┘ └───────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────┐
│ Validates: │
│ • structural correctness (serde) │
│ • closed 17-feature set (no gaps) │
│ • schema version freshness │
│ • code-ref #symbol still exists in tree│
└─────────────────────────────────────────┘
Separate layer (human-facing):
┌─────────────────────────────────────────┐
│ docs/<platform>.md │
│ (operator setup guides — how to deploy)│
│ NOT duplicated in TOML; lives alongside│
└─────────────────────────────────────────┘
When OpenAB gains a new capability (e.g. voice_call), update in one PR:
crates/platform-schema/src/lib.rs— add toEXPECTED_FEATURESdocs/platforms/_template.toml— add a[[openab_features]]blockdocs/platforms/README.md— add a row to the feature table above- All 8
schema/*.tomlfiles — add the feature block with appropriate status:[[openab_features]] feature = "voice_call" status = "not_implemented" # or implemented / partial / workaround / n_a note = "..." source = ["crates/openab-gateway/src/adapters/line.rs#handle_voice"] pr = "#XXXX"
- Bump
SCHEMA_VERSIONinlib.rs+ updateschema_versionin all.tomlfiles
CI enforces completeness: a missing feature block in any platform file fails the build.
When an adapter adds or drops support (e.g. LINE gains streaming):
- Edit only that platform's
schema/<platform>.toml - Update
status,note,source, andprfields - No other files need to change (no version bump required for status-only changes)
- Copy
_template.tomltoschema/<platform>.toml - Fill all
[capability.*]sections (source: official platform docs) - Fill all 17
[[openab_features]]blocks (source: adapter code) - Add
[[quirks]]for platform-specific behaviors - Add the platform to
EXPECTED_PLATFORMSintests/conformance.rs - Add a row to the Platforms table in this README
| Layer | Purpose | Audience |
|---|---|---|
docs/platforms/schema/*.toml |
Machine-readable facts schema | CI, automation, onboarding |
docs/<platform>.md |
Human-readable setup/operator guide | Operators deploying OAB |
These are complementary, not overlapping. TOML captures "what the platform can do + what OpenAB implements"; Markdown captures "how to configure and deploy". Update the TOML when adapter behavior changes; update the Markdown when deployment instructions change.