Skip to content

feat(dispatch): Start DM dispatches for linked provider identities - #2217

Open
sentry-junior[bot] wants to merge 4 commits into
mainfrom
feat/identity-dispatch
Open

sentry-junior[bot] wants to merge 4 commits into
mainfrom
feat/identity-dispatch

Conversation

@sentry-junior

@sentry-junior sentry-junior Bot commented Oct 23, 2026

Copy link
Copy Markdown
Contributor

Step 2 of #2207. Plugin webhook routes get ctx.agent.dispatchForIdentity({ subjectId, idempotencyKey, input, replyAttribution? }). It starts private work in a Slack DM for the person behind one provider account. The result is { status: "created" | "already_exists", id } or { status: "not_linked" }. The call does not throw because the person is untrusted.

Core decides who counts, in code and before any model call:

  • The provider is always the plugin that calls. A plugin cannot name another provider's Identity. Core namespaces (junior, slack) never match.
  • The match uses only the exact provider account id that the OAuth account-linking flow stored (empty tenant, kind user, linked user). Handles, emails, and display names never match.
  • That user must have exactly one Slack Identity in the bot workspace. If there are none or more than one, the result is not_linked.
  • users.info must show a current human member of the bot team: same team, not deleted, not a bot. This repeats the revocation check from Slack ingress.
  • No Slack bot or no team id also gives not_linked. Other Slack and storage errors reach the route.

On a match, core opens the bot DM and creates a normal plugin dispatch with private visibility and a private-direct-conversation credential subject for that Slack user. Binding, validation, and enqueue use the same code as heartbeat dispatches. No second authority path exists.

Not in this PR:

  • Step 3 (action review provenance for the person's instruction) and step 4 (connecting the GitHub webhook and adding the optional reply) come next.
  • Per-plugin rate limits for route dispatches. The route context lives for the whole app, so a heartbeat-style counter does not fit. Today every created dispatch needs a distinct linked member and its own idempotency key. A limit can follow when step 4 shows real traffic.

via BYK.

--

View Junior Session [Sentry]

Plugin webhook routes can call ctx.agent.dispatchForIdentity with a
provider account id. Core binds the provider to the calling plugin and
starts a private Slack DM dispatch only when that Identity is linked
through OAuth to exactly one current Slack member of the bot workspace.
Otherwise it returns not_linked before any model call.

Refs #2207

Co-Authored-By: BYK <burak.kaya@sentry.io>
@vercel

vercel Bot commented Oct 23, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
junior-docs Ready Ready Preview Oct 29, 2026 6:43pm UTC

Request Review

Identity dispatch now applies the allowed domains from main, like Slack
ingress, so a linked member outside JUNIOR_ALLOWED_DOMAINS cannot start
work from a plugin webhook.

Co-Authored-By: Burak Yigit Kaya <126780+BYK@users.noreply.github.com>

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit f4c3a14. Configure here.

Use isNativeIdentityProvider so a plugin named after a native provider,
such as google, cannot start work for core-linked sign-in identities.

Refs #2207
# Conflicts:
#	packages/junior-plugin-api/src/operations.ts
#	packages/junior/src/chat/agent-dispatch/context.ts
#	packages/junior/src/chat/ingress/slack-allowed-domain.ts

Co-Authored-By: BYK <burak.kaya@sentry.io>

@BYK BYK left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Plugin webhook routes get ctx.agent.dispatchForIdentity({ subjectId, idempotencyKey, input, replyAttribution? }). It starts private work in a Slack DM for the person behind one provider account.

We need to rework some of this logic. If the origin is a public GitHub repo, then the work should be considered public, even if we decide to message the author on a private Slack DM. The reverse is a bit tricky though: once we open a public-facing PR or issue from a private DM we still need to make sure no private or proprietary information goes back to public but the "work" itself is public.

@sentry-junior

sentry-junior Bot commented Oct 29, 2026

Copy link
Copy Markdown
Contributor Author

Thanks @BYK. As agreed, this PR stays private-only, and it has no code change for origin visibility.

Per-Conversation visibility will be its own step, before the GitHub webhook work (step 4). Today Conversation visibility comes from the shared junior_destinations.visibility row for the Slack channel. If one public-origin dispatch marked a DM as public, all other Conversations in that DM would read as public. The next normal DM message would then flip the row back to private. The reverse case (a private DM run that writes to a public issue or PR) stays with step 3 / #773 and the "no private data in public output" check. I updated the plan on #2207.

sentry-junior Bot added a commit that referenced this pull request Oct 30, 2026
A cold review found gaps that break a rolling deploy or widen trust.

- Split the Owner step. 4a writes the column, and 4b adds the NOT VALID
  check one release later, because old workers insert roots with no
  Owner while migrations run.
- Drop each column in two releases. conversationReadColumns() selects
  every schema column, so release N stops reads and writes and N+1
  drops it. Add an explicit stop of actor_json writes.
- Pin Guardian labels to the Actor and provenance, never the one system
  Identity id, and keep trusted event text in the Requester instruction
  section.
- Use findLinkedSlackMember() for event author trust, the same check as
  #2217 and #2260.
- Pin Identity keys, the mailbox field, and the full system merge.
- Drop hasActor and list the remaining choices as proposals.

Refs #2439

Co-Authored-By: David Cramer <david@sentry.io>

This branch was successfully deployed

1 active deployment
Preview – junior-docs — ce058475 Deployed Oct 29, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

risk: high PR risk score: high

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant