docs(adr): revise identity-trust-none — three-layer architecture#1291
docs(adr): revise identity-trust-none — three-layer architecture#1291chaodu-agent wants to merge 12 commits into
Conversation
Receiver → Trust Gate → Handler replaces the previous 'gate at handle_message()' design. Addresses all findings from the PR #1263 mob review (howie + 3 LLM reviewers). Key changes: - §4.2: Trust Gate is a dedicated ingress layer upstream of Handler - §5: New architecture diagram showing three-layer separation - §7: Implementation plan starts with Receiver/Handler split - Address #1: gate at actual convergence point (not handle_message) - Address #2: trust lookup keys off per-event platform (not adapter) - Address #3: slash commands gated (Handler is downstream of gate) - Address #4: exhaustive scattered-checks inventory - Address #5: explicit empty-vs-missing semantics - Address #6: phased rollout (Phase 0-3) - Address #7: echo rate-limit + bot exclusion + DM-preferred - Address #8: gateway vs first-class section precedence - Address #9: no static HashSet (runtime construction) - Address #10: structured logging on allow + deny - Address #11-#15: minor fixes (Teams ID, bot semantics, etc.)
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
- Add type-level guarantee (GatedEvent vs InboundEvent) — compile-time enforcement, not just convention (#4) - Clarify Gateway Receiver is one receiver that demuxes by platform (#11) - Fix layer numbering inconsistency — use names, not numbers (#21) - Add sender ID format table with per-platform gotchas (#22, #23, #24) - Clarify is_bot bypass is caller-side, not inside decide() (擺渡-1) - Change echo group fallback to silent drop (avoid UID leakage) (#6)
This comment has been minimized.
This comment has been minimized.
- Add §5 'Event loop binding' section: run_platform generic pipeline, EventReceiver/EventHandler traits, main.rs startup wiring - Gateway platforms: one shared WS, demux by event.platform, fan-out to per-platform Handlers - Fix is_bot bypass: bots skip L3 but STILL enforce L2 scope (擺渡-1 🔴) - Add cross-crate boundary note for Gateway Receiver (擺渡-2 🟡) - Include binding topology summary diagram
This comment has been minimized.
This comment has been minimized.
- GatedEvent: private field in narrow module (not pub(crate)), with read-only accessors and module layout diagram (諸葛村夫-1) - gate_event: use configs.get().surface_allowed() to match real API (擺渡-3) - Phase table: add Phase 0.5 for current partially-wired state on main, clarify Phase 2 means 'refuse to start' (諸葛村夫-2)
seal() lives in the same module as gate_event(), so it should be a plain private fn. pub(super) would unnecessarily expose it to the parent module.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
LINE maintainer feedback (ref: per-platform LINE section)Verified the LINE section against Six LINE-specific refinements the ADR should absorb before implementation: 1. deny-echo: "core does the echo" doesn't hold for LINE — split decision from deliveryLINE's outbound path already exists as 2. deny-echo on LINE must be Reply-only — never PushWhen the reply token is expired (>50s) or consumed, 3. Group identity:
|
wangyuyan-agent
left a comment
There was a problem hiding this comment.
Reviewed the Feishu-relevant parts against the current source (gateway/src/adapters/feishu.rs, src/gateway.rs, src/config.rs). The three-layer direction (Receiver → Trust Gate → Handler) is sound, and the sender-ID table correctly flags that Feishu open_id is per-app. Three points on §7 / the Feishu specifics (details inline):
should_skip_event()isn't a real symbol (the gateway-client filter is inline inrun_gateway_adapter); the cited line refs also don't match the filter location in currentmain.- The Feishu gateway-crate check is more than
feishu.rs:425— there's a sibling group allowlist (feishu.rs:443-448) with no defined destination, and for Feishu the identity is double-gated across two processes (core side fail-open when empty). is_botlives in the Receiver'sInboundEventand drives the gate's L3 bypass, buttrusted_bot_idsis slated to "stay in Handlers" — for Feishu these conflict, sinceis_botcan't be computed withouttrusted_bot_ids.
| - Echoes + drops denied events | ||
| 4. **Remove scattered trust checks** — replaced by the unified Trust Gate: | ||
| - `is_denied_user()` in Discord EventHandler (`discord.rs:2892`) | ||
| - `should_skip_event()` user/channel filter in `gateway.rs` (`:832`, `:1160`) |
There was a problem hiding this comment.
should_skip_event() isn't a symbol in the repo — grep returns zero matches. The gateway-client user/channel filter this refers to is inlined in run_gateway_adapter (src/gateway.rs), not a named function. The cited lines also look off: in current main the allowed_channels/allowed_users filter is at src/gateway.rs:785/:791, while :832/:1160 land on MessageContext construction. Since this drives a "remove these" step, the symbol/line refs should point at the real filter to stay actionable.
| - `is_denied_user()` in Discord EventHandler (`discord.rs:2892`) | ||
| - `should_skip_event()` user/channel filter in `gateway.rs` (`:832`, `:1160`) | ||
| - Inline user allowlist in Slack (`slack.rs:1224`) | ||
| - Feishu L3 check in the gateway crate (`feishu.rs:425`) — must relocate to |
There was a problem hiding this comment.
Two gaps for Feishu here:
- The gateway-crate Feishu check isn't only the user allowlist (
feishu.rs:424-429). The sameparse_message_eventhas a sibling group allowlist right after —feishu.rs:443-448(allowed_groups, matched onchat_id). It's in neither this relocate list nor "stays in Handlers," so its destination is undefined under the "gateway = L1 only" goal. - For Feishu this identity is already filtered a second time by the core
gateway.rsfilter two bullets up — a cross-process double gate (gateway envFEISHU_ALLOWED_USERS+ core[gateway].allowed_users). They can diverge, and the core side fails open when its list is empty (resolve_allow_all=flag.unwrap_or(list.is_empty())).
| - Feishu L3 check in the gateway crate (`feishu.rs:425`) — must relocate to | ||
| core, not just delete (contradicts "gateway = L1 only" model) | ||
| - Discord reaction-dispatch gating (`discord.rs:1241`) | ||
| - Note: `trusted_bot_ids`, `allow_bot_messages`, `allowed_role_ids` **stay in |
There was a problem hiding this comment.
This conflicts with the is_bot design for Feishu. InboundEvent.is_bot is set by the Receiver and used at the Trust Gate to bypass L3, but Feishu marks other bots as sender_type="user" (see the comment in feishu.rs), so is_bot for a non-self bot can only be derived by matching trusted_bot_ids against open_id. If trusted_bot_ids lives only in the Handler (downstream of the gate), the Receiver can't set is_bot correctly and the gate's L3 bypass is unreliable for Feishu. The gateway crate already computes is_bot from trusted_bot_ids at receive time today — so either trusted_bot_ids (or its result) must be available at the Receiver, or the ADR should carve out Feishu's is_bot derivation.
|
Slack-focused review — the general architecture is sound, but there are a few Slack-specific gaps that will bite every deployment. Line references are against 1. Echo delivery via DM is the wrong default for Slack§5 says "DM-preferred, else silent drop." On Slack this breaks onboarding:
Recommendation: For Slack, spec 2.
|
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
- Replace line-number refs with symbol+semantic descriptions (drift-proof) - Rewrite echo section: platform-specific echo trait (LINE=Reply only, Slack=chat.postEphemeral, Discord=DM); leak-safe content by scope - Add is_bot per-platform derivation table (pinned canonical rules) - Document trusted_bot_ids as shared config (resolves Feishu circular dep) - Clarify slash commands scope (Slack doesn't consume them) - Update Slack sender ID: Enterprise Grid composite key (team_id, sender_id) - Add non-message events section (assistant_thread_started must gate) - Add Slack scope notes (Socket Mode only, MPIM=channel) - Add LINE group policy: open/members dual-mode in decide() - Add LINE @mention pre-filter as documented Receiver exception - Feishu: gateway=L1 only, eliminate double-gating, empty list=deny-all Addresses feedback from: - @luffy-aiagent (LINE platform review) - @antigenius0910 (Slack platform review) - @wangyuyan-agent (Feishu platform review)
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
Fixes identified during group review:
F1+F6: Add workspace_id to InboundEvent; define Slack Enterprise Grid
canonical sender_id format and config examples for Grid deployments
F2: Replace HashMap<String, TrustConfig> with enum PlatformTrustConfig
(Base/Line/Slack) — LINE group policy and Slack workspace-scoped
trust now have proper type representations
F3: Add cron bypass in gate_event() — system-initiated events skip
L2/L3 (platform='cron' or sender_id='openab-cron')
F4: Add #[cfg(test)] assume_trusted_for_test() constructor for
GatedEvent — enables Handler unit testing without full pipeline
F5+F9: Change into_inner() to pub(crate); adjust safety claim wording
from 'bypass impossible' to 'accidental bypass compile error'
F7: Change gate_event() signature to take InboundEvent by value —
zero-copy hot path (no .clone() on RawPlatformEvent)
F8: Specify bounded LRU cache (max_capacity + TTL) for rate-limit
state — prevents OOM from random sender_id flooding
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
F1 (critical): Remove sender_id spoofing hole in cron bypass — only check
platform == "cron" since WeCom allows freeform UserIDs that could
match any synthetic value. Update rationale accordingly.
F2: Add WeCom, Google Chat, MS Teams to pinned is_bot derivation and
echo-delivery tables (all 8 platforms now covered).
F3: Fix pseudocode precision — unwrap_or no longer borrows a temporary;
add PlatformTrustConfigs::get() and PlatformTrustConfig::surface_allowed()
delegating method used by gate_event.
…B11/B12/B15 Fixes: - WeCom is_bot: remove enter_agent (user-initiated, not bot); keep only trusted_bot_ids (B5 F1, B12 F1) - SlackTrustConfig::decide(): workspace_users is now strict override (ignore allow_all_users); base fallback supports team_id:user_id composite key for Enterprise Grid (B4 F1, B5 F2, B12 F3) - LINE group policy: fix prose vs code contradiction — unconfigured groups use default_group_policy, not DenyScope (B12 F2) - Reserved platform validation: MUST-level requirement for all external Receivers to reject reserved platform names; cron bypass invariant documented; Phase 1 SHOULD for typed InboundSource enum (B1) - decide() simplified to use self.get() — remove duplicate default logic (B5 F3) - Echo rate-limit key updated to (platform, workspace_id, sender_id) (B12 F4, B15 F2) - Module layout: InboundEvent in mod.rs (public), gate.rs narrow (B12 F5) - into_inner() trust boundary doc: module-level vs crate-level explained, Phase 1 SHOULD for lint/annotation (B11, B15 F1) - Slack Enterprise Grid gotcha: split dense table cell into footnote (B8 F2)
InboundEvent.platform MUST be lowercase — Receivers normalize before constructing. This ensures consistency between gate_event's == "cron" check and PlatformTrustConfigs::get()'s to_lowercase() lookup.
Add comment noting platform field is assigned by gateway routing config, not from webhook payload body — satisfies reserved platform invariant.
|
Note LGTM ✅ — ADR architecture is sound, all platform reviewer feedback addressed, pseudocode matches prose commitments. What This PR DoesRevises the identity-trust-none ADR (merged via #1264) from a single-point router gate to a three-layer adapter architecture (Receiver → Trust Gate → Handler), resolving all findings from the PR #1263 mob review and three subsequent platform-specific reviews (LINE, Slack, Feishu). How It WorksThe core insight is structural: by splitting each adapter into a Receiver (pure transport + L1 auth) and a Handler (platform-specific interaction logic), with a unified Trust Gate between them, the architecture provides a compile-time guarantee that no untrusted event can reach any Handler. This is enforced via Key additions in this revision:
Findings
What's Good (🟢)
Baseline Check
Addressing External Reviewer Feedback@luffy-aiagent (LINE platform review)
✅ All addressed in @antigenius0910 (Slack platform review)
✅ All addressed in @wangyuyan-agent (Feishu platform review)
✅ All addressed in 5️⃣ Three Reasons We Might Not Need This PR
Counterarguments: (1) Superseding is cleaner — one canonical ADR, not two conflicting docs. (2) The pseudocode precision prevents the "ADR says X but code does Y" drift that plagued the original. (3) Platform reviewers validated against real source code — these aren't speculative, they're derived from actual adapter implementations. |
|
Note LGTM ✅ — All findings resolved across 4 fix commits. What This PR DoesRevises the merged identity-trust-none ADR to a three-layer ingress architecture (Receiver → Trust Gate → Handler) with type-level enforcement ( Review SummaryMob review with 8 reviewers covering correctness, architecture, security, docs/UX, spec verification, and operability. All critical and important findings have been resolved in commits Findings Addressed
What's Good
Nits Deferred to Implementation
|
|
Note LGTM ✅ — Thorough revision of the identity-trust-none ADR, successfully addresses all platform-specific review findings with concrete, implementable design. What This PR DoesRevises the identity-trust-none ADR from a conceptual "single router-level gate" design to a fully specified three-layer adapter architecture (Receiver → Trust Gate → Handler) with type-level enforcement ( How It WorksThe ADR splits each adapter into a Receiver (transport + L1 + normalization to Findings
Baseline Check
What's Good (🟢)
5️⃣ Three Reasons We Might Not Need This PR
|
|
Maintainer review — Approving ✅ 🟢 Green — solid, no action needed
🟡 Yellow — non-blocking, please address (here or in #1262)
🔴 Red — blockingNone. Approving now; yellows are doc-level fixes that don't need a re-review. |
…conformance (#1295) * docs(platforms): add capability matrix skeleton * docs(platforms): seed LINE notes from ADR #1291 review * docs(platforms): docs/platforms/wecom.md (schema v1) * docs(platforms): docs/platforms/discord.md (schema v1) * docs(platforms): docs/platforms/_template.md (schema v1) * docs(platforms): docs/platforms/telegram.md (schema v1) * docs(platforms): docs/platforms/googlechat.md (schema v1) * docs(platforms): docs/platforms/feishu.md (schema v1) * docs(platforms): docs/platforms/slack.md (schema v1) * docs(platforms): docs/platforms/line.md (schema v1) * docs(platforms): docs/platforms/teams.md (schema v1) * docs(platforms): docs/platforms/README.md (schema v1) * docs(platforms): single date schema version, drop front-matter — docs/platforms/wecom.md * docs(platforms): single date schema version, drop front-matter — docs/platforms/discord.md * docs(platforms): single date schema version, drop front-matter — docs/platforms/_template.md * docs(platforms): single date schema version, drop front-matter — docs/platforms/telegram.md * docs(platforms): single date schema version, drop front-matter — docs/platforms/googlechat.md * docs(platforms): single date schema version, drop front-matter — docs/platforms/feishu.md * docs(platforms): single date schema version, drop front-matter — docs/platforms/slack.md * docs(platforms): single date schema version, drop front-matter — docs/platforms/line.md * docs(platforms): single date schema version, drop front-matter — docs/platforms/teams.md * docs(platforms): single date schema version, drop front-matter — docs/platforms/README.md * docs(platforms): add _template.toml — machine-readable schema (Option 1, serde) Annotated TOML template that doubles as the human-readable schema reference. Covers all three schemas from README.md (platform-capability / openab-feature- support / platform-quirks) as typed fields + note + source, ready to be validated by serde structs + a cargo conformance test (issue #1322). Schema-only for now; per-platform schema/*.toml + the conformance harness follow once the field set is confirmed. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(platforms): enumerate all 16 openab_features in _template.toml List the complete closed feature set as blank blocks (send_message … group_routing) so the template doubles as a fill-in form, not just one example. Clarify conformance: real schema/*.toml fully deserialize; the template is checked for section/feature-key completeness. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(platforms): source points to file (+optional #symbol), not line numbers Line numbers go stale on any edit above the target. Switch `source` to "file.rs" or "file.rs#symbol" — a symbol name is stable (churns only on rename/delete, exactly the drift worth catching) and greppable, so conformance can actually verify it rather than trivially checking a line exists. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(platforms): tighten _template.toml per review - quirks.kind now required, named values (intrinsic | openab_decision) not A/B - mentions: put typed `method` first for consistent fill order - attachments: keep single headline max_size_mb, require per-type/plan detail in note - Schema 1: add explicit fill guidance (note=how, source=official URL, "?" in note for unverified facts) so agents fill correctly Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(platforms): machine-readable schema/*.toml + conformance tests Land the schema-driven side of the platform knowledge base (issue #1322): - docs/platforms/schema/{line,slack,telegram,discord,feishu,wecom, googlechat,teams}.toml — all 8 platforms converted from their md pages into the typed schema (18 capability sections, the 16-feature closed set, quirks). Every `source` is a "file.rs" / "file.rs#symbol" code-ref, verified against the tree. - crates/platform-schema — the validator + conformance tests: structural validation (required fields, closed enums, closed feature set, unknown-key rejection) + the anti-drift check that every code-ref source still exists (file present, #symbol greps). 3 unit + 5 conformance tests, all green. Uses toml_edit parse-only: no proc-macros, no build scripts, so it needs no C toolchain — runs on minimal CI images. - .github/workflows/platform-schema-conformance.yml — runs the checker on any change to the schema files, the template, or the checker. Excluded from the root workspace so it builds independently of the heavy adapter crates. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * refactor(platform-schema): serde structs as the authoritative schema Switch the validator from toml_edit tree-walking back to serde-derive structs (the agreed design in #1322): each schema/*.toml deserializes into `Platform`, with enums as closed sets and `deny_unknown_fields` for typo rejection. Conformance tests unchanged in intent (structural validity, schema version, closed feature set, present-features-cite-a-source, and the anti-drift check that every code-ref source still exists). 4 unit + 8 conformance tests, all green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(platforms): address review — version, EOF newlines, PR refs, sourcing note Resolves the CHANGES REQUESTED findings on #1295: - F1: bump schema version 2026-07-04 → 2026-07-07 across README (current version + conformance table) and every page's `**Schema version:**` line, so it matches the TOML schema. Findings-log dates are left as-is (real dates, not the schema version). - F2: add trailing newline to the platform pages that were missing one. - F4: replace placeholder `[PR #TBD]` / `[PR: @TBD]` with `#1295`. - F3: add a "Machine-readable schema" note to the README — the `schema/*.toml` files (with `file#symbol` refs) are the machine-checked source of truth; the `.md` `file:line` refs are a point-in-time snapshot for readability. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(platforms): drop the .md pages — schema/*.toml is the single format The per-platform Markdown pages duplicated the schema/*.toml content (their prose now lives in the toml `note` fields, their findings logs in `[[quirks]]`). Remove them and _template.md; the TOML files + conformance crate are the sole source of truth going forward. - Delete docs/platforms/{line,slack,telegram,discord,feishu,wecom,googlechat, teams}.md and _template.md. - Rewrite README as a toml-only index + schema reference (capability sections, the 16-feature set, quirks), pointing at schema/*.toml and _template.toml. - Fix stale "converted from <platform>.md" provenance comments in the toml headers now that those md files are gone. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(platforms): add 4 optional capability fields Promote known platform quirks from prose notes to typed, comparable fields (all optional, so no forced backfill): - attachments.max_count — max attachments per message (Discord = 10) - attachments.outbound_delivery — url | upload (LINE sends media by URL) - edit_message.max_edits — edit cap per message (Feishu = 20) - delete_message.window_sec — deletion window in seconds (WeCom recall = 86400) Backfilled the platforms with known values (LINE, Discord, Feishu, WeCom); others omit them. Template + serde structs updated; 4 unit + 8 conformance tests green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(platforms): add cron_dispatch to the openab-feature closed set #1315 landed telegram cron on main; model it in the schema. Adds cron_dispatch as the 17th closed feature across all 8 platform pages (discord/slack/telegram=implemented, others=not_implemented), the template, README, and bumps schema_version to 2026-07-08. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(platforms): add missing cron_dispatch to README feature table * docs(platforms): add workflow guide for updating features, platforms, and architecture note * docs: add platform schema section to CONTRIBUTING.md * docs(platforms): add architecture diagram to README * ci(platform-schema): add --locked for reproducible conformance builds Build strictly from the committed Cargo.lock and fail if it is stale, rather than silently regenerating it. Partial fix for #1338 (--locked); Rust cache, checkout@v6, and concurrency group tracked there separately. --------- Co-authored-by: luffy-aiagent <luffy-aiagent@users.noreply.github.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Co-authored-by: chaodu-agent <chaodu-agent@users.noreply.github.com>
* feat(trust): Phase 1 (slack) — L3 identity via shared gate Wire Slack into the PlatformTrustConfigs registry and call the shared ingress trust gate from the Slack message path, mirroring the Discord wiring from #1270. Slack was the only configured platform absent from the registry — the gate would have fallen back to the deny-all default had it ever run for slack events. - main.rs: insert "slack" TrustConfig — L2 open (Slack's own channel allowlist stays authoritative in the adapter), L3 mirrors the resolved [slack].allow_all_users/allowed_users, so the gate agrees with Slack's existing user check (behavior-preserving). - slack.rs: thread Arc<AdapterRouter> through run_slack_adapter into handle_message; evaluate gate_incoming after the existing user check (redundant-but-matching, non-regressive). Bots bypass L3 — same rationale as Discord (#1270 review F1): bot admission is allow_bot_messages + trusted_bot_ids, and L3 is human-identity only. - is_dm passed truthfully via Slack conversation-ID prefix (D… = DM), cf. #1270 review F2; decision is identical either way today since the entry is L2-open with allow_dm=true. - tests: pin the L3 bot-bypass and the DM prefix classification. Refs #1361 (first task), umbrella #1356, ADR #1291. * refactor(trust): consolidate l3_gate_applies into trust.rs Self-review finding: the Slack wiring copy-pasted l3_gate_applies from discord.rs (identical 3-line fn + doc). Move the single definition next to Decision in trust.rs and import it from both gate call sites. Each adapter keeps its own pinning test against the shared fn. * docs(platforms): slack.toml reflects Phase 1 shared-gate wiring The platform-facts KB (#1295) landed on main after this branch was cut and records 'Slack does NOT use the shared gate' — which this PR makes stale. Update the trust_gate feature entry (partial → implemented, mirroring discord.toml's phrasing) and the Native-trust-divergence quirk. Conformance suite passes against this branch's code refs. --------- Co-authored-by: chaodu-agent <chaodu-agent@users.noreply.github.com>
Add a [line] config section for L3 identity trust, replacing the uniform GATEWAY_ALLOW_ALL_USERS/GATEWAY_ALLOWED_USERS env vars for LINE. First slice of #1355, mirroring the [telegram] pattern (#1297). - config.rs: LineConfig { allow_all_users, allowed_users } with LINE_ALLOW_ALL_USERS / LINE_ALLOWED_USERS env fallbacks and deny-all default (identity-trust-none ADR). Trust-only by design — channel credentials stay on LINE_CHANNEL_SECRET / LINE_CHANNEL_ACCESS_TOKEN. - main.rs: [line] (or LINE_* env) overrides the uniform GATEWAY_* seed in the trust registry, exactly like the telegram override. When LINE is active and still driven by the legacy GATEWAY_* env, log a Phase 1 deprecation warning (becomes an error in Phase 2, #1356). - docs: config.toml.example, config-reference.md ([line] section), line.md (User Trust section + env table). - tests: TOML parse + all env resolution scenarios in one test fn (env-race safety, same pattern as telegram_resolve_all_scenarios). Group policy (open/members) and Reply-API deny-echo are follow-ups on the issue. Refs #1355, umbrella #1356, ADR #1291. Co-authored-by: chaodu-agent <chaodu-agent@users.noreply.github.com>
…(Phase 1)
Clone the [line] pattern to the three remaining gateway platforms via a
shared PlatformTrustConfig (allow_all_users/allowed_users with
{PREFIX}_ALLOW_ALL_USERS / {PREFIX}_ALLOWED_USERS env fallbacks and
deny-all default). Env prefixes follow the adapters' existing
conventions: WECOM, GOOGLE_CHAT, TEAMS.
main.rs gains a platform_trust_override helper that applies the
first-class section (or its env) over the uniform GATEWAY_* seed, and
logs the shared Phase 1 deprecation warning when an active platform is
still trust-driven by the legacy env (activity signals: WECOM_CORP_ID,
GOOGLE_CHAT_ENABLED, TEAMS_APP_ID — same as has_unified_platform_env).
Trust-only by design: platform credentials stay on the gateway env
vars. Platforms needing richer trust fields later (trusted_bot_ids for
wecom/teams per their issues) graduate to their own struct, as LINE
will for group policy.
Docs: config.toml.example, config-reference.md (combined section with
per-platform sender-ID formats), wecom.md, google-chat.md,
msteams-selfhosted.md.
Refs #1358 #1359 #1360, umbrella #1356, ADR #1291. Stacked on #1365.
Summary
Revises the identity-trust-none ADR (#1264, merged) to adopt a three-layer adapter architecture (Receiver → Trust Gate → Handler), addressing all findings from the mob review on PR #1263 and subsequent platform-specific reviews.
Before vs After
Before (original ADR)
After (this revision)
Key Design Decisions
GatedEventprivate constructor)unsafeplatformas trust lookup keytrusted_bot_ids= shared configis_bot, Handler does admission — no circular dependencyAddressed Review Feedback
LINE (@luffy-aiagent) — comment
"unknown"userId)"open"(group-level trust, unknown allowed) /"members"(per-user, unknown denied)Slack (@antigenius0910) — comment
chat.postEphemeral(only needschat:write)is_botderivation must be pinned(team_id, sender_id); mandateenterprise_user.idwhen availableassistant_thread_startedbypasses GateInboundEvent { is_dm: true }G-prefix = channel (not DM); documented as limitationFeishu (@wangyuyan-agent) — review
allowed_groupsdestination undefined + double-gating + fail-openis_botvstrusted_bot_idscircular dependencytrusted_bot_idsis shared config — Receiver reads it foris_bot, Handler for admissionWhat Changed in the ADR
InboundEventstruct,GatedEventtype-level enforcementis_botper-platform derivation table (pinned canonical rules)trusted_bot_idsdocumented as shared configopen/members) with"unknown"handling[gateway]precedence rulesRelated