feat(channels)!: migrate mattermost from in-process adapter to sidecar#5315
Conversation
Replaces the 954-line in-process `librefang-channels::mattermost` (WebSocket gateway to `wss://<host>/api/v4/websocket` with an `authentication_challenge` JSON frame after the upgrade, `posted` event parsing, REST `POST /api/v4/posts` outbound, `POST /api/v4/users/me/typing` typing indicators, Bearer auth) with an out-of-process Python sidecar at `sdk/python/librefang/sidecar/adapters/mattermost.py` (stdlib-only — `urllib.request` for REST, hand-rolled RFC 6455 WS client over `socket` + `ssl`, same pattern as webex/discord/slack #5302/5299/5309). Same pattern as zulip #5310 / line #5312. Behaviour parity (each citation against the pre-migration tree): - `GET /api/v4/users/me` startup credential probe + bot-id self-skip (mattermost.rs:107-128, 206-210) - WebSocket `authentication_challenge` JSON frame after upgrade (mattermost.rs:335-353) - `posted` event filter with double-decoded `data.post` JSON parse (mattermost.rs:186-202) - DM vs group from `data.channel_type == "D"` (mattermost.rs:222-223) - Channel-allowlist filter (mattermost.rs:212-215) - Slash-command routing `/cmd args` → Command (text otherwise) - 16383-char chunking matches MAX_MESSAGE_LEN (mattermost.rs:22) - Multi-bot `account_id` injection (#5003, mattermost.rs:419-424) - Exponential 1 s → 60 s reconnect backoff (mattermost.rs:306-438) - ChannelType::Custom("mattermost") preserved as `channel_type = "mattermost"` so existing routing / channel_role_mapping keys resolve Improvements over the Rust adapter: 1. Outbound `root_id` round-trip via `thread_id` — Rust `send` at mattermost.rs:446-462 hard-coded no `root_id`, dropping the thread even when the inbound message was in one. Sidecar surfaces inbound `post.root_id` (or post.id when the inbound was a thread root) as `thread_id` and `on_send` posts `root_id` populated. 2. 429 `Retry-After` honoured on every REST path (Rust had none — chunked sends just lost the throttled chunk). 3. Bounded inbound dedupe on `post.id` (SEEN_MESSAGES_MAX=10000 / EVICT=5000) — Rust emit at mattermost.rs:425 was unconditional, so a WS reconnect double- delivered. 4. Explicit 15 s `urlopen` timeouts on every REST call. Cascade removal: - `src/mattermost.rs` deleted; `lib.rs` `pub mod mattermost`, `channels-allowlist.txt` entry, cargo `channel-mattermost` features in both `librefang-channels` and `librefang-api` (incl. membership in `all-channels` / `all-channels-no-email` / `mini`) - `MattermostConfig` struct + Default impl + `ChannelsConfig.mattermost` field + matching Default entry; validation hook; mod.rs serde tests - `channel_bridge.rs`: `MattermostAdapter` import, spawn block, `find_channel_info!` arm, `check_channel!` arm, default-empty test assertion - `routes/channels.rs`: `ChannelMeta` entry + 4 match arms (`is_some` / serialize / `len` / `ser`); `SIDECAR_CATALOG` entry added - `routes/config.rs` `ch!(mattermost)` call - kernel `channel_sender` `for_each_channel_field!` entry + `EXPECTED` name-list entry - CLI TUI `ChannelDef` row; migrated-to-sidecar comment grown - `librefang-channels` README / AGENTS updated - `librefang-channels/http_client.rs` warn_ws_proxy_bypass smoke test removed (only caller was mattermost) - `librefang-migrate` openclaw importer: legacy `[channels.mattermost]` block recorded as a skipped sidecar channel (same shape as the IRC removal) instead of emitting TOML the kernel would refuse to deserialize - Docs (EN + ZH): `configuration/page.mdx`, `configuration/channels/page.mdx`, `configuration/security/page.mdx`, `integrations/channels/enterprise/page.mdx` — `[channels.mattermost]` blocks replaced with `[[sidecar_channels]]` redirects Operator action required: an existing `[channels.mattermost]` block is no longer recognised — re-declare as `[[sidecar_channels]]` running `python3 -m librefang.sidecar.adapters.mattermost`. `MATTERMOST_TOKEN` moves to `~/.librefang/secrets.env`. Test plan: - [x] `cargo check -p librefang-types -p librefang-channels -p librefang-api -p librefang-kernel -p librefang-cli -p librefang-migrate --lib` — clean - [x] `cargo xtask channel-policy` — passed (`mattermost` removed from allowlist) - [x] `cargo test --lib -p librefang-types -p librefang-channels -p librefang-kernel` — passed - [x] `cd sdk/python && pytest tests/test_mattermost_adapter.py` — 73 passed - [ ] Live Mattermost WS smoke test (operator)
Deploying librefang-docs with
|
| Latest commit: |
da8b604
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://126fc867.librefang-docs-web.pages.dev |
| Branch Preview URL: | https://feat-channels-mattermost-sid.librefang-docs-web.pages.dev |
The adapter declared `typing` in `capabilities` so the daemon will route `TypingCmd` to us, but `on_command` was inherited from the base `SidecarAdapter` which only handles `send`. Result: every typing ping from the daemon was silently dropped — a regression vs the Rust adapter at mattermost.rs:464-485 which actually called the `/api/v4/users/me/typing` endpoint. Implement `on_command` to dispatch both `Send` (the existing `on_send` path) and `TypingCmd` (the new `_post_typing` helper), matching the discord precedent at discord.py:1126-1135. Tests: 73 → 77 passing (added 4 covering capabilities declaration, Send routing, TypingCmd routing, and unknown-command no-op).
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: da8b604bf7
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| with self._make_ws(self.ws_url, headers={}) as ws: | ||
| self._run_session(ws, emit) | ||
| backoff = INITIAL_BACKOFF_SECS | ||
| except Exception as e: # noqa: BLE001 — transport varies |
There was a problem hiding this comment.
Apply reconnect backoff after normal websocket drops
When _run_session() returns normally (e.g., EOF/close/socket drop paths at lines 900-908), this loop immediately reconnects because backoff is only applied in the except branch. That bypasses the documented exponential reconnect behavior and can create a tight reconnect storm against Mattermost during outages or server-initiated disconnects.
Useful? React with 👍 / 👎.
|
|
||
|
|
||
| class MattermostAdapter(SidecarAdapter): | ||
| capabilities: list = ["thread", "typing"] |
There was a problem hiding this comment.
Handle typing commands when advertising typing support
This adapter advertises the typing capability, so the daemon can route TypingCmd commands to it, but the class never overrides on_command; the base SidecarAdapter.on_command only dispatches Send. As a result, typing commands are silently ignored and _post_typing() is never called, so users lose typing-indicator behavior that existed in the Rust adapter.
Useful? React with 👍 / 👎.
…5317) * feat(channels)!: migrate signal from in-process adapter to sidecar Replaces the 975-line in-process `librefang-channels::signal` (polling loop against signal-cli-rest-api with SSRF guard rejecting loopback/RFC-1918/link-local/CGNAT/IPv6-ULA, `POST /v2/send` outbound, optional Bearer API key, optional base64 attachments) with an out-of-process Python sidecar at `sdk/python/librefang/sidecar/adapters/signal.py` (stdlib-only — `urllib.request` for REST, `socket` + `ipaddress` for the SSRF classifier). Same pattern as line #5312 / zulip #5310 / mattermost #5315. Behaviour parity (each citation against the pre-migration tree): - SSRF guard: scheme + DNS-resolved address validation matches signal.rs:27-98. Default-deny on private/loopback unless `SIGNAL_ALLOW_LOCAL=1` is set explicitly. - `GET /v1/receive/{phone}` polling with default 2 s cadence (signal.rs:171, 332). - Self / allowlist / empty-text filters (signal.rs:361-376). - Slash-command routing `/cmd args` → Command (signal.rs:383-396). - `POST /v2/send` with `{message, number, recipients}` (signal.rs:204-244). - Multi-bot `account_id` injection (#5003, signal.rs:417-422). - Optional Bearer auth via `SIGNAL_API_KEY`. Improvements over the Rust adapter: 1. Inbound dedupe on `envelope.timestamp` with `SEEN_MESSAGES_MAX=10000`/`EVICT=5000` — Rust emit at signal.rs:398-415 was unconditional, so a retry redelivered duplicates. 2. 429 `Retry-After` honoured on both poll and send paths (Rust had no 429 handling at all). 3. Explicit 15 s `urlopen` timeouts on every REST call. 4. 1–60 s exponential backoff on transport / non-2xx errors — Rust just `continue`-d on every error and spun at `poll_interval` against a wedged daemon. Known gap: the Rust adapter's inline base64 attachment support (`Image`/`Voice`/`Video`/`Audio`/`Animation`/`File`/`FileData`/ `MediaGroup`) is not wired through the sidecar yet — non-text content falls back to a `(Unsupported content type)` placeholder. Follow-up will restore the base64 round-trip end-to-end. Cascade removal: - `src/signal.rs` deleted; `lib.rs` mod, `channels-allowlist.txt` entry, both `Cargo.toml` features (incl. `all-channels` / `all-channels-no-email` / `mini`) - `SignalConfig` struct + Default + `ChannelsConfig.signal` field + `default_signal_poll_interval_secs` helper + 3 unit tests - `channel_bridge.rs` import / spawn block / `find_channel_info!` / `check_channel!` / default-empty assertion - `routes/channels.rs` `ChannelMeta` entry + 4 match arms; `SIDECAR_CATALOG` entry added - `routes/config.rs` `ch!(signal)` removed - `channel_sender.rs` macro entry + EXPECTED list entry - CLI TUI `ChannelDef` - `librefang-channels` README / AGENTS sentence - `librefang-migrate` openclaw importer (both JSON-block and YAML-file paths) — Signal now records a `report.skipped` entry (mirrors IRC / Mattermost) - Docs EN + ZH (6 files) Test plan: - [x] `cargo check -p librefang-types -p librefang-channels -p librefang-api -p librefang-kernel -p librefang-cli -p librefang-migrate --lib` — clean - [x] `cargo xtask channel-policy` — passed (`signal` removed) - [x] `pytest sdk/python/tests/test_signal_adapter.py` — 61 passed - [ ] Live signal-cli-rest-api smoke test (operator) * fix(channels): make signal SSRF guard fail-closed on unparseable addr `_is_private_or_loopback` returned `False` (= "public, allow") when `ipaddress.ip_address` rejected the input string. In production `socket.getaddrinfo` always hands back a well-formed IPv4/IPv6 literal so the path was unreachable, but the SSRF guard's contract is default-deny — a future change that lets a scoped IPv6 literal (`fe80::1%eth0`) through `getaddrinfo` would have silently allowed link-local addresses. Fail-CLOSED: any address the classifier cannot interpret is treated as private. Tests cover the contract directly + the scoped-literal case. Tests: 61 → 62 (1 new for fail-closed on garbage inputs). * docs(channels): correct Signal sidecar override + attachment notes The two Signal-specific sections in the channels overview were authored against the in-process Rust adapter and still claimed: 1. `Signal Plain-Text Default` — that you can override `output_format` via `[channels.signal.overrides]`. The `[channels.signal]` block was deleted in this migration, and `SidecarChannelConfig` does not expose an `overrides` field, so the snippet was inaccurate. The PlainText default itself still applies because it keys off the sidecar entry's `channel_type = "signal"` (`default_output_format_for_channel` in `crates/librefang-channels/src/formatter.rs`); only the *override mechanism* is currently absent on sidecar channels. 2. `Signal Media Attachments` — that base64 attachment delivery for `Image / Voice / Video / Audio / Animation / File / FileData / MediaGroup` works. The sidecar currently routes anything other than text to `(Unsupported content type)`; the PR description already lists base64 attachment parity as a known follow-up. Updates EN + ZH overview pages plus the two cross-reference sentences in EN + ZH `core/page.mdx` that pointed at these anchors. * fix(channels): drop stale signal refs missed by sidecar migration Two leftovers from the in-process Signal removal broke 4 CI lanes (Quality, Workspace coverage, Test/Windows shard 1, Test/macOS), all with the same compile error: error[E0609]: no field `signal` on type `ChannelsConfig` 1. `librefang-migrate/src/openclaw.rs` — the `test_roundtrip_migrate_output_into_real_structs` test was still reading `cfg.channels.signal` even though the field is gone. The migrator now records the legacy `signal:` JSON block as a `report.skipped` entry (already covered by `test_signal_block_records_skipped_after_sidecar_migration`), so the inline read-back has no struct to land on. Replaced with a pointer to the dedicated skipped-channel test. 2. `librefang-cli/src/main.rs` — `cmd_channel_setup("signal")` still wrote a `[channels.signal]` block with `phone_env`, `socket_path`, `default_agent` fields the kernel would refuse to deserialize, and the `cmd_channel_list` summary still listed `signal` as a configurable in-process channel. Dropped the `"signal"` match arm and removed `signal` from both the list-view vector and the interactive picker — same shape the previous discord / slack / telegram migrations used. Orphan i18n keys `section-setup-signal` in `locales/{en,zh-CN}/main.ftl` are left in place to match the precedent set by discord/slack (`section-setup-discord`, `section-setup-slack` also kept as orphans); a single sweep across migrated channels is a separate cleanup. --------- Co-authored-by: Evan <[email protected]>
Replaces the 758-line in-process `librefang-channels::qq` adapter (token mint + WebSocket gateway + HELLO/IDENTIFY/READY handshake + heartbeat + DISPATCH for 4 event types + REST send with markdown stripping) with an out-of-process Python sidecar at `sdk/python/librefang/sidecar/adapters/qq.py`. Same pattern as ntfy #5224, telegram #5241, gotify #5263, mastodon #5264, bluesky #5277, reddit #5281, twitch #5297, rocketchat #5298, discord #5299, nextcloud #5301, slack #5302, webex #5309, line #5312, zulip #5310, mattermost #5315, signal #5317. Behaviour parity (every claim cited against the pre-migration tree): - Token fetch: POST bots.qq.com/app/getAppAccessToken (qq.rs:542-557). - Gateway: GET /gateway with Bearer auth (qq.rs:559-573). - WS handshake: HELLO(op=10) → IDENTIFY(op=2) with token = "QQBot <access_token>", intents bitmask, shard=[0,1] → READY dispatch (qq.rs:413-433). - Heartbeat: op=1 frame at the server-supplied interval, carrying last seen `s` from dispatch frames (qq.rs:359-368). - 4 dispatch event types: MESSAGE_CREATE / AT_MESSAGE_CREATE → /channels/{channel_id}/messages, DIRECT_MESSAGE_CREATE → /dms/{guild_id}/messages, GROUP_AT_MESSAGE_CREATE → /v2/groups/{group_openid}/messages, C2C_MESSAGE_CREATE → /v2/users/{user_openid}/messages (qq.rs:194-224). - Bot-mention `/` strip + slash-command routing (qq.rs:227). - User allowlist + multi-bot account_id metadata (#5003, qq.rs:400-405). - Outbound markdown stripping pipeline preserves order: think tags → code blocks → inline code → bold → italic → headings → table separators → links → blockquotes → HRs → triple-newline collapse (qq.rs:137-180). - 2000-char chunking (qq.rs:26), 1–60 s exponential reconnect backoff (qq.rs:282). Improvements over the Rust adapter ================================== 1. **Reply context actually round-trips.** The Rust `parse_dispatch_event` (qq.rs:182-246) computed `reply_endpoint` and `msg_id` but the dispatch loop bound them to `_endpoint` / `_msg_id` (qq.rs:399) and dropped them on the floor; `send` (qq.rs:497-498) then expected `user.platform_id` to be encoded as `"<endpoint>|<msg_id>"` and silently no-op'd when the delimiter wasn't there. The Rust adapter therefore failed every real outbound — only the synthetic wiremock tests at qq.rs:686-712 exercised the working shape. The sidecar surfaces the reply endpoint as `channel_id` and the QQ `msg_id` as `thread_id` on the inbound event so the bridge round-trips them through to `on_send`, which posts to `{api_base}{channel_id}` with the correct passive-reply `msg_id`. 2. **Inbound dedupe on `msg.id`.** Rust emitted every parsed event unconditionally (qq.rs:399-410); WS reconnect could replay. Bounded local set, SEEN_MAX=10000 / EVICT=5000. 3. **429 Retry-After honoured on every REST path.** Token, gateway, and outbound send all sleep + retry once; second 429 logs-and-continues (matches webex / line / mattermost / signal #5303). 4. **Explicit 15s urlopen timeouts on every REST call.** Rust pre-configured reqwest's 30s default at qq.rs:71; sidecar passes `timeout=SEND_TIMEOUT_SECS` (15 s) so a misbehaving endpoint trips an explicit error. Cascade removal ================ - `src/qq.rs` deleted; `lib.rs` mod, `channels-allowlist.txt` entry, both `Cargo.toml` features removed. - `QqConfig` struct + Default + `ChannelsConfig.qq` field gone. - `channel_bridge.rs` import / spawn block / `check_channel!` / find_channel_info!(qq) (none existed) removed. - `routes/channels.rs` `ChannelMeta` entry + 4 match arms (`is_some` / serialize / `len` / `ser`) removed; SIDECAR_CATALOG `qq` entry added. - `routes/config.rs` `ch!(qq)` removed. - `channel_sender.rs` `for_each_channel_field!` macro entry + `EXPECTED` name-list entry removed. - CLI / TUI had no QQ wizard arm to remove. - librefang-migrate had no QQ block to handle (QQ wasn't in the OpenClaw schema). - Docs EN + ZH (6 files): `[channels.qq]` blocks replaced with `[[sidecar_channels]]` redirects; integrations tutorial rewritten for the sidecar. New env-var knobs (`[sidecar_channels.env]`) ============================================= - `QQ_APP_ID` (required) - `QQ_ALLOWED_USERS` (optional, CSV) - `QQ_ACCOUNT_ID` (optional) - `QQ_INTENTS` (optional, decimal/hex bitmask override) Secret (`~/.librefang/secrets.env`) ==================================== - `QQ_APP_SECRET` (required) Operator action required ======================== An existing `[channels.qq]` block is no longer recognised — re-declare as `[[sidecar_channels]]` running `python3 -m librefang.sidecar.adapters.qq`. See `sdk/python/librefang/sidecar/adapters/qq.py` header for the exact config. `ChannelType::Custom("qq")` is preserved via `channel_type = "qq"` on the sidecar entry so existing routing and `channel_role_mapping` keys continue to resolve. Verification ============ - `cargo check -p librefang-types -p librefang-channels -p librefang-api -p librefang-kernel -p librefang-cli --lib` — clean - `cargo xtask channel-policy` — passed (`qq` removed from allowlist) - `pytest sdk/python/tests/test_qq_adapter.py -q` — **77 passed** (covers env parsing, markdown stripping, parse_qq_event for all 4 event types + edge cases, _mark_seen LRU eviction, _fetch_token + _fetch_gateway happy path / 429 / non-200 / missing field, _post_message chunking + 429 retry-once + persistent-429 fail-open + 5xx fail-open, on_send wiring with markdown stripping at the outbound boundary, WS gateway flow via mock _WebSocketClient — HELLO → IDENTIFY token+intents+ shard, DISPATCH emission, msg.id dedupe, RECONNECT/INVALID_SESSION, heartbeat after interval, --describe schema, capabilities contract). Co-authored-by: Evan <[email protected]>
…migration (closes #5316) Four `librefang-migrate::openclaw::tests` cases used mattermost as their in-process channel witness, but #5315 migrated mattermost to a sidecar and the production migrate code now pushes a SkippedItem instead of emitting [channels.mattermost]. With no other in-process channel in the small fixtures, migrate_channels_from_json returned None, the report had zero ItemKind::Channel imports, and the JSON5 full-migration imported-count fell from 7 to 6 — turning main red. - create_legacy_yaml_workspace: add messaging/whatsapp.yaml as the new in-process witness (telegram/discord/slack/mattermost are all sidecar-skipped). test_scan_workspace now asserts channels.len() == 5 with whatsapp membership. - test_json5_channel_extraction: add whatsapp to the inline JSON5; mattermost joins the sidecar-skip loop; ch_table.contains_key flips to whatsapp; secret-count stays at 5 (MATTERMOST_TOKEN still extracted via the sidecar-skipped path) and the new MATTERMOST_TOKEN=mm-token assertion against secrets.env makes that explicit. - test_json5_full_migration: assertion 7 -> 6; count-comment rewritten to enumerate the 7 skips (telegram, discord, slack, irc, mattermost, imessage, bluebubbles) and the 6 in-process imports (whatsapp, signal, matrix, feishu, google_chat, msteams). - test_policy_migration: replace mattermost witness with signal (still in-process, also accepts dm_policy); mattermost added to the sidecar-skip loop alongside discord/slack. Verified: 36 openclaw::tests + 51 lib tests + 6 idempotency integration tests green; `cargo clippy -p librefang-migrate --all-targets -- -D warnings` clean.
Summary
Replaces the 954-line in-process
librefang-channels::mattermost(WebSocket gateway towss://<host>/api/v4/websocketwithauthentication_challengeJSON frame after the upgrade,postedevent parsing, RESTPOST /api/v4/postsoutbound,POST /api/v4/users/me/typingtyping indicators, Bearer auth) with an out-of-process Python sidecar atsdk/python/librefang/sidecar/adapters/mattermost.py(stdlib-only —urllib.requestfor REST, hand-rolled RFC 6455 WS client oversocket+ssl). Same pattern as twitch (#5297), rocketchat (#5298), discord (#5299), nextcloud (#5301), slack (#5302), webex (#5309), line (#5312), zulip (#5310).Behaviour parity
GET /api/v4/users/mestartup probe + bot-id self-skip (mattermost.rs:206-210)authentication_challengeJSON frame after the upgrade (mattermost.rs:335-353)postedevent filter with double-decodeddata.postJSON parse (mattermost.rs:186-202)data.channel_type == "D"(mattermost.rs:222-223)/cmd args→Command(text otherwise)MAX_MESSAGE_LEN(mattermost.rs:22)account_idinjection (mattermost.rs:419-424, fix(channels): override account_id() in non-Telegram multi-bot adapters #5003)Improvements over the Rust adapter (with file/line evidence)
root_idround-trip viathread_id— Rustsend(mattermost.rs:446-462) droppedroot_id, so the bot's reply lost its thread for the common case. Sidecar surfaces inboundpost.root_id(or post.id for thread-root) asthread_idandon_sendpostsroot_idpopulated. Mirrors reddit / rocketchat / nextcloud / webex.Retry-Afterhonoured on every REST path — Rust had no 429 handling, so a throttled/postslost the chunk (mattermost.rs:165-169 only logs at WARN). Same shape as fix(channels): honour Retry-After across sidecar polling adapters #5303.post.iddedupe — Rust emit at mattermost.rs:425 was unconditional; a WS reconnect double-delivered.SEEN_MESSAGES_MAX = 10 000/EVICT = 5 000.urlopentimeouts — Rust relied onreqwest's default (none). A hung Mattermost REST endpoint would otherwise hang the producer thread forever.Cascade removal
src/mattermost.rsdeleted;lib.rsmod,channels-allowlist.txtentry, bothCargo.tomlfeatures (incl.all-channels/all-channels-no-email/mini) removedMattermostConfigstruct + Default +ChannelsConfig.mattermostfield + Default + validation hook +test_mattermost_config_defaults/_serde/_channel_proxy_roundtripsunit tests gonechannel_bridge.rsimport / spawn block /find_channel_info!/check_channel!/ default-empty assertion removedroutes/channels.rsChannelMetaentry + 4 match arms removed;SIDECAR_CATALOGmattermostentry addedroutes/config.rsch!(mattermost)removedchannel_sender.rsfor_each_channel_field!macro entry +EXPECTEDname-list entry removedChannelDefremovedlibrefang-channels/README.md+AGENTS.md"migrated to sidecars" sentence updatedlibrefang-channels/http_client.rswarn_ws_proxy_bypass_smoketest removed (only remaining caller was mattermost)librefang-migrate/openclaw.rs— legacy[channels.mattermost]block recorded as a skipped sidecar channel (same shape as the IRC removal) instead of emitting TOML the kernel would refuse to deserialize[channels.mattermost]blocks replaced with[[sidecar_channels]]redirects; env-var table marked (sidecar)New env-var knobs
From
[sidecar_channels.env]:MATTERMOST_SERVER_URL(required),MATTERMOST_ALLOWED_CHANNELS(optional),MATTERMOST_ACCOUNT_ID(optional).From
~/.librefang/secrets.env:MATTERMOST_TOKEN(required).Operator action required
An existing
[channels.mattermost]block is no longer recognised — re-declare as[[sidecar_channels]]runningpython3 -m librefang.sidecar.adapters.mattermost. Seesdk/python/librefang/sidecar/adapters/mattermost.pyheader for the exact config.ChannelType::Custom("mattermost")is preserved viachannel_type = "mattermost"on the sidecar entry, so existing routing andchannel_role_mappingkeys that referencemattermostcontinue to resolve.Verification
cargo check -p librefang-types -p librefang-channels -p librefang-api -p librefang-kernel -p librefang-cli -p librefang-migrate --lib— cleancargo xtask channel-policy— passed (mattermostremoved from allowlist)cargo test --lib -p librefang-types -p librefang-channels -p librefang-kernel— passedcd sdk/python && python3 -m pytest tests/test_mattermost_adapter.py -q— 73 passedDiff
29 files changed, +2051 / −1257.