feat(trust): Phase 1 (discord) — L3 identity via shared gate [DRAFT: canary before merge]#1270
Conversation
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
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.
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.
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.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
|
LGTM ✅ — Clean, minimal wiring of the shared trust gate (L3 identity) for Discord ingress. Non-regressive by construction and canary-verified. All reviewer findings are migration items for Phase 1c, not current-PR blockers. What This PR DoesAdds Discord to the shared How It Works
Findings
Finding Details🟡 F1:
|
* 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>
Phase 1 for Discord (#1264, #1269). Routes Discord ingress through
AdapterRouter::gate_incomingfor the L3 (identity) layer. Draft — to be canary-verified on one bot before merge (Discord is prod-critical).Design (why L3-only for Discord)
Discord's L2 is richer than the flat
allowed_channelsmodel: threads are admitted by parent channel and DMs byallow_dm. A flatsurface_allowed(thread_channel_id)would wrongly deny thread messages whenallow_all_channels=false. So:discordregistry entry is L2-open + allow_dm=true → the gate enforces L3 only; Discord's own channel/thread/DM logic stays in the adapter.[discord].allow_all_users/allowed_users.Non-regressive by construction
The gate call sits at the dispatch spawn, after Discord's existing pre-dispatch user check already ran with the same config. So the gate can only agree — it cannot deny a message that was already admitted. (Empty
sender_idfail-closed can't trigger — Discord messages always have an author.)Canary plan (before marking ready)
Deploy to one bot (e.g. a test bot) and confirm identical admit/deny:
Deferred
Testing
--features unified -D warningsclean; 92/92 Discord unit tests passRefs #1264 #1269