Skip to content

fix(bidirectional): drop messages from KNOWN mayor bots (bo-pwib)#7

Open
openclaw-dv wants to merge 1 commit into
mainfrom
fix/bidirectional-filter-mayor-bots
Open

fix(bidirectional): drop messages from KNOWN mayor bots (bo-pwib)#7
openclaw-dv wants to merge 1 commit into
mainfrom
fix/bidirectional-filter-mayor-bots

Conversation

@openclaw-dv

Copy link
Copy Markdown
Collaborator

Summary

Wesley msg 729: outbound Slack posts via midgard mayor's bot (user_id U0B385LBXPT) trigger 📬 + 👀 reactions on themselves because the bo-nb97 self-only filter only matches THIS Worker's own bot — not the mayor bots posting THROUGH the shared Slack workspace.

Bug chain that lead here:

Bead Layer Fixed
bo-nb97 (PR #6) bidirectional: peer-bot delivery Hermes can DM midgard mayor
bo-uu4i (gastown-utils #23) poll-side self-echo mayor's context isn't polluted
bo-xyh2 (gastown-utils #24) poll-side 🤔 reaction no 🤔 on bot's own writes
bo-pwib (this) bidirectional: mayor-bot outbounds the FIRST stage still treated mayor outbounds as inbound

The bidirectional Worker treats midgard mayor's outbound DM as a real Wesley→mayor inbound, fires 📬 + 👀 reactions, AND re-routes the bot's own message back into its own gt-receiver inbox. Self-conversation loop.

Fix

New KNOWN_MAYOR_BOT_USER_IDS env var (comma-separated U… ids) + getMayorBotUserIds(env) resolver that:

  1. Env override — parses the comma-separated list when set. Skips the auth.test round-trip on cold start.
  2. Auto-discover — calls auth.test against each per-mayor *_MAYOR_BOT_TOKEN env binding present. Mirrors the bo-nb97 getBotUserId shape.
  3. Union into a Set<string> the predicate isKnownMayorBot(env, userId) checks against.

Predicate wired into all three event handlers:

const botId = await getBotUserId(env);
if (botId && ev.user === botId) return;          // bo-nb97
if (await isKnownMayorBot(env, ev.user)) return; // bo-pwib
  • handleMessage (thread replies)
  • handleDm (DMs to per-mayor apps)
  • handleChannelMention (@-mentions of a per-mayor app)

handleMessage scope extension

The bead called out handleDm + handleChannelMention. I extended to handleMessage too because mayor↔mayor thread replies have the same loop shape — midgard's reply in a yggdrasil-watched alert thread would otherwise be re-routed. Default-deny is the safer floor; if mayor-to-mayor cross-talk becomes a feature, add an opt-in env override.

Effect

  • Mayor's own outbound posts (Wesley msg 729) → silent. No 📬, no 👀, no re-route.
  • Wesley's real messages → unchanged.
  • Peer bots (Hermes, future third-party Slack integrations) → unchanged from bo-nb97.
  • Mayor-to-mayor cross-talk → dropped.

Test plan

No unit tests added — alert-hub has no test harness configured in package.json. Verification via live Slack post-deploy:

  • Deploy via the CI workflow.
  • Set KNOWN_MAYOR_BOT_USER_IDS="U0B385LBXPT" via wrangler vars OR bind the per-mayor tokens so auth.test resolves them.
  • Post via MIDGARD_MAYOR_BOT_TOKEN to Wesley's DM. Confirm no 📬 / 👀 reaction.
  • Wesley replies. Confirm reactions DO fire + gt-receiver row lands.
  • Post a Hermes message. Confirm bo-nb97 contract preserved.

Cross-repo context

Bead bo-pwib is tracked in the boardroom rig's bd issue tracker. The polecat that produced this PR (boardroom/polecats/dementus) closed bo-pwib with a back-pointer to this PR.

🤖 Generated with Claude Code

Wesley msg 729: my outbound Slack posts via midgard mayor's bot
(user_id U0B385LBXPT) trigger 📬 + 👀 reactions on themselves
because bidirectional's bo-nb97 self-only filter only matches
THIS Worker's own bot — not the mayor bots posting THROUGH the
shared Slack workspace.

Sequence of bugs that lead here:

  * bo-nb97 fixed PEER-bot delivery — Hermes can DM midgard
    mayor now.
  * bo-uu4i fixed the POLL-side self-echo so the mayor's own
    context isn't polluted with its own writes.
  * bo-xyh2 stopped the 🤔 reaction on poll-side self-echoes.
  * bo-pwib (this) — the FIRST stage still routes midgard
    mayor's outbound DM as a fresh inbound DM. The bidirectional
    Worker treats it as a real Wesley → mayor message, fires
    📬 + 👀 reactions, AND re-routes the bot's own message back
    into its own gt-receiver inbox. Self-conversation loop.

Fix
---

New ``KNOWN_MAYOR_BOT_USER_IDS`` env var (comma-separated U…
ids) + ``getMayorBotUserIds(env)`` resolver that:

  1. Parses the env override when set.
  2. Auto-discovers via auth.test against any per-mayor
     ``*_MAYOR_BOT_TOKEN`` env binding present
     (YGGDRASIL_MAYOR_BOT_TOKEN, MIDGARD_MAYOR_BOT_TOKEN). One
     auth.test per token on cold start, cached for the isolate
     lifetime. Mirrors the bo-nb97 ``getBotUserId`` shape.
  3. Returns the union as a Set<string> the predicate
     ``isKnownMayorBot(env, userId)`` checks against.

Predicate is wired into all three event handlers — handleMessage
(thread replies), handleDm (DMs to per-mayor apps), and
handleChannelMention (@-mentions of a per-mayor app). Same shape
on each call site:

    const botId = await getBotUserId(env);
    if (botId && ev.user === botId) return;       // bo-nb97
    if (await isKnownMayorBot(env, ev.user)) return;  // bo-pwib

The bo-nb97 self-only check runs first (cheap module-level cache
hit on the common path). The mayor-bot check fires after — slower
on first call (one auth.test per per-mayor token) but cached for
the isolate lifetime. Set ``KNOWN_MAYOR_BOT_USER_IDS`` via wrangler
vars to skip the auth.test round-trip entirely.

handleMessage scope extension
-----------------------------

The bead called out handleDm + handleChannelMention. I extended
the filter to handleMessage too because mayor↔mayor thread
replies in alert threads have the same loop shape — midgard's
reply to a yggdrasil-watched alert thread would otherwise be
treated as an inbound thread reply and re-routed.

If a use case for "a mayor bot writes a thread reply that
SHOULD be received by another mayor" emerges, we add an
opt-in env override (KNOWN_MAYOR_BOT_USER_IDS_EXCEPT) — but
the default-deny is the safer floor.

Effect
------

After deploy + setting ``KNOWN_MAYOR_BOT_USER_IDS`` (or just
binding the per-mayor tokens so auth.test resolves them):

  * Mayor's own outbound posts (Wesley msg 729) — silent. No
    📬, no 👀, no re-route into the mayor's own inbox.
  * Wesley's real messages — unchanged, still routed.
  * Peer bots (Hermes, future third-party Slack integrations) —
    unchanged from bo-nb97, still routed.
  * Mayor-to-mayor cross-talk — dropped. If this becomes a
    feature, it can be re-enabled via the exception env-var
    above.

## Cross-repo context

Bead bo-pwib is tracked in the boardroom rig's bd issue tracker.
The polecat that produced this PR (boardroom/polecats/dementus)
closed bo-pwib with a back-pointer to this PR. Sibling fixes in
this self-echo / bot-routing thread (chronological):

  * alert-hub PR #6 (bo-nb97) — bidirectional self-only filter.
  * dv-gastown-utils PR #23 (bo-uu4i) — poll-side self-filter.
  * dv-gastown-utils PR #24 (bo-xyh2) — silent-ack self-echoes
    so the 🤔 reaction doesn't fire on bot's own writes.
  * alert-hub PR #X (this) — drop mayor-bot outbounds before
    they ever reach the gt-receiver pipeline.

## Test plan

No unit tests added — the alert-hub repo has no test harness
configured in package.json scripts; verification is via live
Slack post-deploy.

- [ ] Deploy via the CI deploy workflow (Doppler-mounted CF
  creds).
- [ ] Set ``KNOWN_MAYOR_BOT_USER_IDS="U0B385LBXPT"`` (midgard
  mayor — known from Wesley msg 729) via ``wrangler vars`` OR
  bind the per-mayor tokens so auth.test resolves them.
- [ ] Post a message via ``MIDGARD_MAYOR_BOT_TOKEN`` to Wesley's
  DM channel. Confirm no 📬 / 👀 reaction fires on the bot's own
  message.
- [ ] Have Wesley send a real reply in the same thread. Confirm
  reactions DO fire on Wesley's message + the gt-receiver row
  lands.
- [ ] Post a Hermes message in a midgard-watched channel.
  Confirm reactions fire + the message routes (bo-nb97
  contract preserved).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant