feat(trust): Phase 1 (gateway) — route gateway ingress through the shared gate#1267
Conversation
… trust gate Wires the shared L2/L3 gate (#1266) into the unified gateway path: - AdapterRouter gains a PlatformTrustConfigs registry (via with_trust builder — new()'s signature unchanged) + gate_incoming() ingress gate - process_gateway_event now enforces L2/L3 via router.gate_incoming(); should_skip_event keeps only bot-filter + @mention gating (its channel/user checks are neutered in the unified path) - registry built at startup from GATEWAY_* env, keyed per gateway platform Behavior-preserving: registry defaults mirror today's should_skip_event (allow-all default); is_dm passed false so DMs are evaluated as channels exactly as today. Discord/Slack routing + dispatch-path privatization + should_skip_event L2/L3 removal follow in later PRs. Deny-flip is Phase 3. Refs #1264
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.
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.
…F1/F2) F1 (blocker): Discord's is_denied_user has a !is_bot bypass (bot admission is handled by allow_bot_messages + trusted_bot_ids). The shared L3 gate is human-identity only, so running it on bots wrongly dropped trusted bot-to-bot messages when allow_all_users=false (multi-agent). Guard the gate with !sender.is_bot. F2: pass the in-scope is_dm instead of hardcoded false (benign today with the L2-open discord entry, but avoids latent risk). Note: #1267 (gateway) is unaffected — should_skip_event's user check never had a bot bypass, so the gateway gate already matched it for bots.
|
LGTM ✅ — Phase 1 trust gate correctly wires the shared ingress gate into the gateway path. Behavior-preserving confirmed by independent review across correctness, architecture, docs/UX, security/CI, and spec compliance. What This PR DoesWires the Phase 0 trust module ( How It Works
Findings
Finding Details🟡 F1:
|
| # | Previous Finding | Resolution |
|---|---|---|
| 🟡 F1 (Round 1) | Gateway-only scope not documented in ADR | ✅ Tracked in #1269 |
| 🟡 F2 (Round 1) | is_dm=false hardcode leaves allow_dm untestable |
✅ Addressed in bc49f0a — TODO comment added |
| 🟡 F3 (Round 1) | Batched dispatch paths ungated | ✅ Tracked in #1269 — clarified: gateway Thread/Lane modes ARE gated (gate runs before dispatcher.submit in process_gateway_event) |
Spec Alignment Summary
ADR Contract Compliance:
- ✅ Phase 0 types consumed correctly (
PlatformTrustConfigs,Decision,TrustConfig::decide()) - ✅ Behavior-preserving (Phase 0→1 boundary respected)
- ✅ Gate placement matches ADR's "single router gate" pattern for gateway
- ✅ Phase 2/3 deferrals correct and tracked (trust: make the ingress gate structurally un-bypassable (Discord/Slack + privatize dispatch entries) #1269)
- ✅ Gateway-only scope documented via trust: make the ingress gate structurally un-bypassable (Discord/Slack + privatize dispatch entries) #1269
- ✅
is_dm=falseworkaround has TODO for Phase 2
Phase 0→1 Contract:
- ✅ API: all Phase 0 exports consumed correctly
- ✅ Behavior: defaults match; fail-closed invariants preserved
- ✅ Deferred items tracked with blocking relationship to Phase 3
Baseline Check
- PR opened: 2026-07-01
- Main already has:
trust.rsmodule (Phase 0, feat(trust): Phase 0 — shared TrustConfig + PlatformTrustConfigs #1266) — types and tests only, not wired - Net-new value: Phase 1 wiring — trust gate now live on gateway ingress for 6 platforms
Verdict: LGTM ✅ — Reviewed by 5 independent reviewers across correctness, architecture, docs/UX, security/CI, and spec compliance. Four new 🟡 findings are all non-blocking (env edge case, comment hygiene, Phase 3 prep documentation). Core behavior-preserving invariant confirmed. Previous round's 3 🟡 findings all addressed (TODO in bc49f0a, tracking in #1269). Ready to merge; 🟡 items can be addressed in follow-up or Phase 3 prep.
…canary before merge] (#1270) * feat(trust): Phase 1 (discord) — gate L3 identity via shared gate Routes Discord ingress through AdapterRouter::gate_incoming for the L3 (identity) layer, keyed under "discord" in the trust registry. - registry "discord" entry: L2 open + allow_dm=true (Discord's richer channel/thread/DM logic stays in the adapter — the flat allowed_channels model can't express thread-by-parent admission); L3 mirrors resolved [discord].allow_all_users/allowed_users - gate call added at the Discord dispatch spawn, redundant-but-matching with Discord's existing pre-dispatch user check → non-regressive by construction (cannot deny what already passed) Behavior-preserving. Phase 1c makes the gate authoritative and removes the scattered user check; richer Discord L2 modeling + dispatch privatization tracked in #1269. Refs #1264 #1269 * fix(trust): Discord gate skips bots + passes real is_dm (review #1270 F1/F2) F1 (blocker): Discord's is_denied_user has a !is_bot bypass (bot admission is handled by allow_bot_messages + trusted_bot_ids). The shared L3 gate is human-identity only, so running it on bots wrongly dropped trusted bot-to-bot messages when allow_all_users=false (multi-agent). Guard the gate with !sender.is_bot. F2: pass the in-scope is_dm instead of hardcoded false (benign today with the L2-open discord entry, but avoids latent risk). Note: #1267 (gateway) is unaffected — should_skip_event's user check never had a bot bypass, so the gateway gate already matched it for bots. * test(trust): name + test the Discord L3 bot-bypass (review #1270 F4) Extract the gate's bot-skip into l3_gate_applies(is_bot) and add a regression test (l3_gate_skips_bots_admits_humans) locking in that bots bypass the shared L3 gate (mirrors is_denied_user's !is_bot), so trusted/mode-admitted bots aren't denied when allow_all_users=false. --------- Co-authored-by: chaodu-agent <chaodu-agent@users.noreply.github.com>
Phase 1 of #1264 (plan), scoped to the gateway path (Telegram/LINE/Feishu/WeCom/Google Chat/Teams). Builds on Phase 0 (#1266).
Behavior-preserving — no message that's accepted today is denied, and vice-versa.
What this does
AdapterRoutergains aPlatformTrustConfigsregistry (via awith_trust()builder, sonew()'s signature and all its callers stay unchanged) + a singlegate_incoming(platform, channel_id, is_dm, sender_id) -> Decisioningress gate.process_gateway_event()now enforces L2 (scope) + L3 (identity) viarouter.gate_incoming().should_skip_event()keeps only its structural checks (bot filter + @mention gating); its channel/user checks are neutered in the unified path (passed allow-all).GATEWAY_*env the bridge already uses, keyed per gateway platform.Why it addresses F1 (from #1266 review)
The gateway's single choke point is
process_gateway_event— the gate now runs there for every gateway event before any dispatch path. Discord/Slack (two-entry submit-vs-handle_message) + making the dispatch entriespub(crate)follow in the next PR.Behavior-preserving details
should_skip_event(allow_all_*default true).is_dm = falsein Phase 1 → gateway DMs are evaluated against the channel allowlist exactly as today; theallow_dmsurface semantics arrive with the per-platform trust flip.sender_idis now denied even underallow_all_users(Phase-0 F7 fail-closed). Gateway events always carry a sender, so no practical impact.Deferred
gate_incoming+ privatizingsubmit/handle_message(next PR)should_skip_event's now-neutered L2/L3 + unusedGatewayEventContextfields (Phase 1c)Testing
cargo clippy --features unified -D warningscleanopenab-coresuite green except the pre-existing macOS-onlysecrets::resolve_exec_nonzero_exitRefs #1264