docs: add Discord channel audience guides
This commit is contained in:
@@ -0,0 +1,181 @@
|
||||
# Channel adapters
|
||||
|
||||
> **Status:** Current shared channel types plus the Discord reference/compatibility implementation. A shared gateway adapter registry, Telegram parity, and Matrix channel integration remain unimplemented or unproven.
|
||||
>
|
||||
> **Last verified:** 2026-08-10 against the source and focused tests listed in [Evidence](#evidence).
|
||||
>
|
||||
> **Audience:** Developers implementing or reviewing channel integrations.
|
||||
|
||||
The gateway remains the policy and runtime boundary. Channel code translates native events, applies its native admission checks, and delivers normalized ingress/egress; it must not choose a provider, harness, process, or native runtime session on behalf of the gateway.
|
||||
|
||||
## Current source boundaries
|
||||
|
||||
| Boundary | Current authority | Current claim |
|
||||
| --------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| Shared contract | [`packages/types/src/channel/channel.dto.ts`](../../../packages/types/src/channel/channel.dto.ts), [`channel-adapter.ts`](../../../packages/types/src/channel/channel-adapter.ts) | DTOs, ports, lifecycle health, and delivery error codes exported by `@mosaicstack/types`. |
|
||||
| Discord translation | [`plugins/discord/src/index.ts`](../../../plugins/discord/src/index.ts) | First adapter implementing the shared lifecycle/egress seam, with an optional direct ingress port and a tested Socket.IO compatibility path. |
|
||||
| Discord behavior | [`plugins/discord/src/index.test.ts`](../../../plugins/discord/src/index.test.ts) | Current allowlist, pairing, role, thread, route, attachment, replay-envelope, egress, retry, and health behavior. |
|
||||
| Gateway compatibility | [`apps/gateway/src/chat/chat.gateway.ts`](../../../apps/gateway/src/chat/chat.gateway.ts), [`chat.gateway-auth.ts`](../../../apps/gateway/src/chat/chat.gateway-auth.ts) | `/chat` Socket.IO service/session authentication, signed Discord envelope validation, trusted binding selection, raw chat dispatch, and raw stream egress. |
|
||||
| Host registry | [`apps/gateway/src/plugin/plugin.interface.ts`](../../../apps/gateway/src/plugin/plugin.interface.ts), [`plugin.module.ts`](../../../apps/gateway/src/plugin/plugin.module.ts), [`plugin.service.ts`](../../../apps/gateway/src/plugin/plugin.service.ts) | Lifecycle-only `IChannelPlugin[]` hosting. This is not a universal `OfficialChannelAdapter` registry. |
|
||||
| Telegram | [`plugins/telegram/src/index.ts`](../../../plugins/telegram/src/index.ts), [`package.json`](../../../plugins/telegram/package.json) | Raw legacy Telegraf/Socket.IO behavior only; no shared-contract or authenticated gateway parity. |
|
||||
| Matrix | No current channel adapter boundary in the cited implementation | Matrix-related fleet/runtime code is not evidence of a gateway channel adapter. |
|
||||
|
||||
Executable source and tests outrank older architecture or quarantine pages. The canonical architecture summary is [`architecture/channel-protocol.md`](../architecture/channel-protocol.md).
|
||||
|
||||
## Shared contract
|
||||
|
||||
`@mosaicstack/types` currently exports the channel types through [`packages/types/src/channel/index.ts`](../../../packages/types/src/channel/index.ts) and [`packages/types/src/index.ts`](../../../packages/types/src/index.ts).
|
||||
|
||||
The lifecycle and port seams are:
|
||||
|
||||
```typescript
|
||||
interface OfficialChannelAdapter {
|
||||
readonly name: string;
|
||||
start(): Promise<void>;
|
||||
stop(): Promise<void>;
|
||||
health(): Promise<ChannelAdapterHealthDto>;
|
||||
}
|
||||
|
||||
interface ChannelIngressPort {
|
||||
receive(ingress: ChannelIngressDto): Promise<void>;
|
||||
}
|
||||
|
||||
interface ChannelEgressPort {
|
||||
send(egress: ChannelEgressDto): Promise<void>;
|
||||
}
|
||||
```
|
||||
|
||||
The current DTO boundary includes:
|
||||
|
||||
- `ChannelMessageDto` for normalized native messages and JSON-safe metadata;
|
||||
- `ChannelAttachmentDto` for bounded external attachment references;
|
||||
- `ChannelAuthorizedPrincipalDto` for the already-authorized channel actor and `viewer`/`operator`/`admin` role;
|
||||
- `ChannelBindingDto` for configuration-owned workspace/channel/logical-agent binding;
|
||||
- `ChannelResponseTargetDto` for a channel and optional thread;
|
||||
- `ChannelConversationRouteDto` for binding, logical agent, stable conversation ID, authorization channel, and response target;
|
||||
- `ChannelIngressDto` for correlation, native message ID, operation, principal, message, and route; and
|
||||
- `ChannelEgressDto` for correlation, normalized output, and route.
|
||||
|
||||
Current operations are `message.send`, `approval.create`, and `session.stop`. Current delivery errors are `invalid_route`, `destination_unavailable`, and `delivery_failed`; there is no shared revoked-auth error code or executable protocol-version contract in this surface.
|
||||
|
||||
### Stable route rule
|
||||
|
||||
The Discord adapter derives:
|
||||
|
||||
```text
|
||||
<logical-agent-id>:discord:<response-channel-id>
|
||||
```
|
||||
|
||||
The binding address is derived from the configured guild, parent channel, and logical-agent instance. The route intentionally omits runtime provider, harness, model, process, and native runtime-session identifiers. Gateway durable-session and provider layers own those identities.
|
||||
|
||||
This is a route-integrity rule, not proof that the gateway has a universal channel session API. A future adapter must derive its route from trusted configuration and native channel/thread identity; it must not accept a caller-selected logical agent or runtime target.
|
||||
|
||||
## Current Discord implementation
|
||||
|
||||
### Native ingress
|
||||
|
||||
`DiscordPlugin` currently:
|
||||
|
||||
1. ignores bot-authored and non-guild messages;
|
||||
2. resolves a thread's actual parent text channel, while leaving normal category parents out of authorization;
|
||||
3. applies default-deny guild, parent-channel, and user allowlists;
|
||||
4. resolves configuration-owned `instanceId`/`agentConfigId` bindings and paired-user roles before thread creation or dispatch;
|
||||
5. applies message and mention-thread limits before side effects;
|
||||
6. creates/reuses a mention thread or preserves an existing thread target; and
|
||||
7. maps the event to a `ChannelIngressDto` when an `ingressPort` dependency is supplied.
|
||||
|
||||
The normalized message uses `channelName: "discord"`, the response target as `channelId`, `senderKind: "user"`, `markdown` for non-empty text, `image`/`file` for attachment-only input, mapped attachments, and metadata containing the native channel message ID and guild ID. It does not currently claim a universal metadata shape for mentions, embeds, channel type, or replies.
|
||||
|
||||
### Socket.IO compatibility path
|
||||
|
||||
The gateway-hosted plugin is currently constructed without a direct `ChannelIngressPort` in [`plugin.module.ts`](../../../apps/gateway/src/plugin/plugin.module.ts), so it uses the established compatibility path:
|
||||
|
||||
1. `start()` connects to `${DISCORD_GATEWAY_URL}/chat` with `auth.discordServiceToken`.
|
||||
2. Normal sends emit a signed `message` envelope; approval and stop emit `discord:approve` and `discord:stop` envelopes.
|
||||
3. The HMAC-SHA-256 signature covers the ordered Discord payload using `DISCORD_SERVICE_TOKEN`.
|
||||
4. The gateway verifies the service token/signature, allowlists, binding/operation role, stable conversation ID, attachment bounds, and replay key before dispatch.
|
||||
5. The gateway selects the binding's trusted `agentConfigId`, verifies its name equals the logical-agent instance, and creates/resumes the conversation without generic provider routing for Discord ingress.
|
||||
6. Agent output currently returns as raw Socket.IO `agent:start`, `agent:text`, and `agent:end` events. The plugin buffers the text and calls its typed Discord egress at stream end.
|
||||
|
||||
This compatibility path carries enough normalized identity to preserve current security and routing behavior, but it is not a gateway-produced `ChannelIngressDto`/`ChannelEgressDto` flow through a shared host registry.
|
||||
|
||||
### Egress and health
|
||||
|
||||
`DiscordPlugin.send()` is a typed `ChannelEgressPort` implementation. It validates the route and message alignment before destination lookup, sends at a 1,900-character boundary, retries transient 429/5xx/network failures up to three times, uses one deterministic enforced nonce per correlation/chunk, and does not retry permanent failures. It reports `connected`, `degraded`, or `disconnected` from Discord client readiness and gateway socket connectivity.
|
||||
|
||||
The host wrapper currently exposes only `name`, `start`, `stop`, and optional project provisioning. It does not expose `health()`, inject the direct ingress port, produce `ChannelEgressDto` values from the gateway, or provide `healthAll`/`getAdapter` semantics.
|
||||
|
||||
## Adapter authoring boundary
|
||||
|
||||
For a future official adapter, preserve these current architectural boundaries:
|
||||
|
||||
1. Normalize native messages to the shared DTOs and preserve native message ID, correlation ID, channel/thread identity, attachments, and response target.
|
||||
2. Enforce native guild/room/channel/user/pairing/role policy before thread/room creation or gateway dispatch.
|
||||
3. Select the logical agent from trusted configuration; never from a caller-controlled provider, model, harness, or route field.
|
||||
4. Keep the route stable when the gateway changes runtime provider or harness.
|
||||
5. Validate egress route and destination before sending; bound chunks, retries, and attachment metadata.
|
||||
6. Return sanitized errors and expose non-throwing lifecycle health for ordinary disconnected state.
|
||||
7. Add happy-path and failure-path tests for authorization ordering, replay/idempotency, route integrity, native side effects, egress, reconnect, and health.
|
||||
|
||||
These are implementation constraints for future work, not evidence that the missing shared registry already exists.
|
||||
|
||||
## Parity status
|
||||
|
||||
### Telegram: raw legacy adapter, not shared parity
|
||||
|
||||
The current Telegram source:
|
||||
|
||||
- launches Telegraf and a Socket.IO client;
|
||||
- reads `TELEGRAM_BOT_TOKEN` and `TELEGRAM_GATEWAY_URL` through the gateway plugin factory, whose URL default is `http://localhost:14242`;
|
||||
- accepts text messages only and ignores attachment-only messages;
|
||||
- maps each Telegram `chat.id` to `telegram-<chatId>`;
|
||||
- emits a raw `{ conversationId, content, role: "user" }` object rather than `ChannelIngressDto`;
|
||||
- has no shared DTO import, channel binding, principal/role policy, native message ID, attachment mapping, route-safe egress, or health method; and
|
||||
- has no package test file in this checkout; its script is `vitest run --passWithNoTests`.
|
||||
|
||||
Its Socket.IO connection does not send the Discord service token or a BetterAuth session. A configured `TELEGRAM_BOT_TOKEN` therefore must not be described as an authenticated official channel. Shared Telegram parity requires a separate implementation and focused security/contract tests.
|
||||
|
||||
### Matrix: no current channel adapter
|
||||
|
||||
No current gateway adapter, shared-port wiring, channel binding, identity resolver, persistence boundary, authentication path, or focused channel test establishes Matrix as a Mosaic channel. Matrix code elsewhere in the repository belongs to other transport/runtime work and must not be promoted into channel-adapter instructions without a separate contract and evidence.
|
||||
|
||||
### Shared registry: draft/unimplemented
|
||||
|
||||
The current `PLUGIN_REGISTRY` is an array of `IChannelPlugin` lifecycle wrappers. It is not a registry of `OfficialChannelAdapter` instances and does not inject `ChannelIngressPort`/`ChannelEgressPort`, aggregate adapter health, or remove the gateway's Discord-specific auth/envelope/approval/stop/replay branches.
|
||||
|
||||
A future registry must specify binding and credential ownership, ingress/egress injection, health/error semantics, compatibility with the existing Socket.IO clients, and tests proving that adapters cannot bypass gateway authorization or route validation before it can be documented as current architecture.
|
||||
|
||||
## Safe verification commands
|
||||
|
||||
The focused package commands used by the current Discord evidence are:
|
||||
|
||||
```bash
|
||||
pnpm --filter @mosaicstack/types build
|
||||
pnpm --filter @mosaicstack/types typecheck
|
||||
pnpm --filter @mosaicstack/discord-plugin typecheck
|
||||
pnpm --filter @mosaicstack/discord-plugin lint
|
||||
pnpm --filter @mosaicstack/discord-plugin test
|
||||
cd apps/gateway && pnpm exec vitest run \
|
||||
src/plugin/discord-ingress.security.spec.ts \
|
||||
src/chat/chat.gateway-redaction.spec.ts \
|
||||
src/__tests__/integration/tess-cross-surface.integration.test.ts
|
||||
```
|
||||
|
||||
These commands do not require starting Gateway, Discord, Telegram, Matrix, a queue, or a database. The Discord package test includes its configured coverage thresholds; the gateway command is a focused Vitest run rather than a claim of full repository integration coverage.
|
||||
|
||||
## Evidence
|
||||
|
||||
- [`packages/types/src/channel/channel.dto.ts`](../../../packages/types/src/channel/channel.dto.ts) — shared DTOs, operations, route fields, and metadata types.
|
||||
- [`packages/types/src/channel/channel-adapter.ts`](../../../packages/types/src/channel/channel-adapter.ts) — adapter lifecycle, ingress/egress ports, and delivery errors.
|
||||
- [`plugins/discord/src/index.ts`](../../../plugins/discord/src/index.ts) — native translation, auth ordering, compatibility envelope, route-safe egress, retry, and health.
|
||||
- [`plugins/discord/src/index.test.ts`](../../../plugins/discord/src/index.test.ts) — focused Discord contract and behavior tests.
|
||||
- [`apps/gateway/src/chat/chat.gateway.ts`](../../../apps/gateway/src/chat/chat.gateway.ts) — service/session auth, signed envelope validation, replay, trusted agent selection, and raw stream events.
|
||||
- [`apps/gateway/src/chat/chat.gateway-auth.ts`](../../../apps/gateway/src/chat/chat.gateway-auth.ts) — timing-safe Discord service-token and BetterAuth session checks.
|
||||
- [`apps/gateway/src/plugin/discord-ingress.security.spec.ts`](../../../apps/gateway/src/plugin/discord-ingress.security.spec.ts) — gateway security and privileged-operation tests.
|
||||
- [`apps/gateway/src/__tests__/integration/tess-cross-surface.integration.test.ts`](../../../apps/gateway/src/__tests__/integration/tess-cross-surface.integration.test.ts) — current Discord durable-session integration.
|
||||
- [`apps/gateway/src/plugin/plugin.interface.ts`](../../../apps/gateway/src/plugin/plugin.interface.ts), [`plugin.module.ts`](../../../apps/gateway/src/plugin/plugin.module.ts), and [`plugin.service.ts`](../../../apps/gateway/src/plugin/plugin.service.ts) — lifecycle-only host registry.
|
||||
- [`plugins/telegram/src/index.ts`](../../../plugins/telegram/src/index.ts) and [`plugins/telegram/package.json`](../../../plugins/telegram/package.json) — raw Telegram behavior and no-test script.
|
||||
- [Canonical channel protocol architecture](../architecture/channel-protocol.md) — shared contract and explicit current/draft boundary.
|
||||
- [Discord ingress security](../../ADMIN-GUIDE/security/discord-ingress.md) — administrator-facing config and auth claims.
|
||||
- [Discord conversations](../../USER-GUIDE/workflows/discord-conversations.md) — user-facing routing behavior.
|
||||
- [Developer Guide](../README.md)
|
||||
Reference in New Issue
Block a user